mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add warning evaluation for pod specs
This commit is contained in:
		
							
								
								
									
										257
									
								
								pkg/api/pod/warnings.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								pkg/api/pod/warnings.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,257 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2021 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package pod
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
			
		||||
	api "k8s.io/kubernetes/pkg/apis/core"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/core/pods"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetWarningsForPod(ctx context.Context, pod, oldPod *api.Pod) []string {
 | 
			
		||||
	if pod == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		oldSpec *api.PodSpec
 | 
			
		||||
		oldMeta *metav1.ObjectMeta
 | 
			
		||||
	)
 | 
			
		||||
	if oldPod != nil {
 | 
			
		||||
		oldSpec = &oldPod.Spec
 | 
			
		||||
		oldMeta = &oldPod.ObjectMeta
 | 
			
		||||
	}
 | 
			
		||||
	return warningsForPodSpecAndMeta(nil, &pod.Spec, &pod.ObjectMeta, oldSpec, oldMeta)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetWarningsForPodTemplate(ctx context.Context, fieldPath *field.Path, podTemplate, oldPodTemplate *api.PodTemplateSpec) []string {
 | 
			
		||||
	if podTemplate == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		oldSpec *api.PodSpec
 | 
			
		||||
		oldMeta *metav1.ObjectMeta
 | 
			
		||||
	)
 | 
			
		||||
	if oldPodTemplate != nil {
 | 
			
		||||
		oldSpec = &oldPodTemplate.Spec
 | 
			
		||||
		oldMeta = &oldPodTemplate.ObjectMeta
 | 
			
		||||
	}
 | 
			
		||||
	return warningsForPodSpecAndMeta(fieldPath, &podTemplate.Spec, &podTemplate.ObjectMeta, oldSpec, oldMeta)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var deprecatedNodeLabels = map[string]string{
 | 
			
		||||
	`beta.kubernetes.io/arch`:                  `deprecated since v1.14; use "kubernetes.io/arch" instead`,
 | 
			
		||||
	`beta.kubernetes.io/os`:                    `deprecated since v1.14; use "kubernetes.io/os" instead`,
 | 
			
		||||
	`failure-domain.beta.kubernetes.io/region`: `deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
 | 
			
		||||
	`failure-domain.beta.kubernetes.io/zone`:   `deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
 | 
			
		||||
	`beta.kubernetes.io/instance-type`:         `deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var deprecatedAnnotations = []struct {
 | 
			
		||||
	key     string
 | 
			
		||||
	prefix  string
 | 
			
		||||
	message string
 | 
			
		||||
}{
 | 
			
		||||
	{
 | 
			
		||||
		key:     `scheduler.alpha.kubernetes.io/critical-pod`,
 | 
			
		||||
		message: `non-functional in v1.16+; use the "priorityClassName" field instead`,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		key:     `seccomp.security.alpha.kubernetes.io/pod`,
 | 
			
		||||
		prefix:  `container.seccomp.security.alpha.kubernetes.io/`,
 | 
			
		||||
		message: `deprecated since v1.19; use the "seccompProfile" field instead`,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		key:     `security.alpha.kubernetes.io/sysctls`,
 | 
			
		||||
		message: `non-functional in v1.11+; use the "sysctls" field instead`,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		key:     `security.alpha.kubernetes.io/unsafe-sysctls`,
 | 
			
		||||
		message: `non-functional in v1.11+; use the "sysctls" field instead`,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta *metav1.ObjectMeta, oldPodSpec *api.PodSpec, oldMeta *metav1.ObjectMeta) []string {
 | 
			
		||||
	var warnings []string
 | 
			
		||||
 | 
			
		||||
	// use of deprecated node labels in selectors/affinity/topology
 | 
			
		||||
	for k := range podSpec.NodeSelector {
 | 
			
		||||
		if msg, deprecated := deprecatedNodeLabels[k]; deprecated {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("spec", "nodeSelector").Key(k), msg))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil {
 | 
			
		||||
		n := podSpec.Affinity.NodeAffinity
 | 
			
		||||
		if n.RequiredDuringSchedulingIgnoredDuringExecution != nil {
 | 
			
		||||
			for i, t := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
 | 
			
		||||
				for j, e := range t.MatchExpressions {
 | 
			
		||||
					if msg, deprecated := deprecatedNodeLabels[e.Key]; deprecated {
 | 
			
		||||
						warnings = append(
 | 
			
		||||
							warnings,
 | 
			
		||||
							fmt.Sprintf(
 | 
			
		||||
								"%s: %s is %s",
 | 
			
		||||
								fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms").Index(i).
 | 
			
		||||
									Child("matchExpressions").Index(j).
 | 
			
		||||
									Child("key"),
 | 
			
		||||
								e.Key,
 | 
			
		||||
								msg,
 | 
			
		||||
							),
 | 
			
		||||
						)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for i, t := range n.PreferredDuringSchedulingIgnoredDuringExecution {
 | 
			
		||||
			for j, e := range t.Preference.MatchExpressions {
 | 
			
		||||
				if msg, deprecated := deprecatedNodeLabels[e.Key]; deprecated {
 | 
			
		||||
					warnings = append(
 | 
			
		||||
						warnings,
 | 
			
		||||
						fmt.Sprintf(
 | 
			
		||||
							"%s: %s is %s",
 | 
			
		||||
							fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution").Index(i).
 | 
			
		||||
								Child("preference").
 | 
			
		||||
								Child("matchExpressions").Index(j).
 | 
			
		||||
								Child("key"),
 | 
			
		||||
							e.Key,
 | 
			
		||||
							msg,
 | 
			
		||||
						),
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for i, t := range podSpec.TopologySpreadConstraints {
 | 
			
		||||
		if msg, deprecated := deprecatedNodeLabels[t.TopologyKey]; deprecated {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf(
 | 
			
		||||
				"%s: %s is %s",
 | 
			
		||||
				fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("topologyKey"),
 | 
			
		||||
				t.TopologyKey,
 | 
			
		||||
				msg,
 | 
			
		||||
			))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// use of deprecated annotations
 | 
			
		||||
	for _, deprecated := range deprecatedAnnotations {
 | 
			
		||||
		if _, exists := meta.Annotations[deprecated.key]; exists {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(deprecated.key), deprecated.message))
 | 
			
		||||
		}
 | 
			
		||||
		if len(deprecated.prefix) > 0 {
 | 
			
		||||
			for k := range meta.Annotations {
 | 
			
		||||
				if strings.HasPrefix(k, deprecated.prefix) {
 | 
			
		||||
					warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(k), deprecated.message))
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// removed volume plugins
 | 
			
		||||
	for i, v := range podSpec.Volumes {
 | 
			
		||||
		if v.PhotonPersistentDisk != nil {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: non-functional in v1.16+", fieldPath.Child("spec", "volumes").Index(i).Child("photonPersistentDisk")))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// duplicate hostAliases (#91670, #58477)
 | 
			
		||||
	if len(podSpec.HostAliases) > 1 {
 | 
			
		||||
		items := sets.NewString()
 | 
			
		||||
		for i, item := range podSpec.HostAliases {
 | 
			
		||||
			if items.Has(item.IP) {
 | 
			
		||||
				warnings = append(warnings, fmt.Sprintf("%s: duplicate ip %q", fieldPath.Child("spec", "hostAliases").Index(i).Child("ip"), item.IP))
 | 
			
		||||
			} else {
 | 
			
		||||
				items.Insert(item.IP)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// duplicate imagePullSecrets (#91629, #58477)
 | 
			
		||||
	if len(podSpec.ImagePullSecrets) > 1 {
 | 
			
		||||
		items := sets.NewString()
 | 
			
		||||
		for i, item := range podSpec.ImagePullSecrets {
 | 
			
		||||
			if items.Has(item.Name) {
 | 
			
		||||
				warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name))
 | 
			
		||||
			} else {
 | 
			
		||||
				items.Insert(item.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// imagePullSecrets with empty name (#99454#issuecomment-787838112)
 | 
			
		||||
	for i, item := range podSpec.ImagePullSecrets {
 | 
			
		||||
		if len(item.Name) == 0 {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: invalid empty name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// duplicate volume names (#78266, #58477)
 | 
			
		||||
	if len(podSpec.Volumes) > 1 {
 | 
			
		||||
		items := sets.NewString()
 | 
			
		||||
		for i, item := range podSpec.Volumes {
 | 
			
		||||
			if items.Has(item.Name) {
 | 
			
		||||
				warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", fieldPath.Child("spec", "volumes").Index(i).Child("name"), item.Name))
 | 
			
		||||
			} else {
 | 
			
		||||
				items.Insert(item.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
 | 
			
		||||
	if value, ok := podSpec.Overhead[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | 
			
		||||
		warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", fieldPath.Child("spec", "overhead").Key(string(api.ResourceMemory)), value.String()))
 | 
			
		||||
	}
 | 
			
		||||
	if value, ok := podSpec.Overhead[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | 
			
		||||
		warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", fieldPath.Child("spec", "overhead").Key(string(api.ResourceEphemeralStorage)), value.String()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, p *field.Path) bool {
 | 
			
		||||
		// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
 | 
			
		||||
		if value, ok := c.Resources.Limits[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceMemory)), value.String()))
 | 
			
		||||
		}
 | 
			
		||||
		if value, ok := c.Resources.Requests[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "requests").Key(string(api.ResourceMemory)), value.String()))
 | 
			
		||||
		}
 | 
			
		||||
		if value, ok := c.Resources.Limits[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceEphemeralStorage)), value.String()))
 | 
			
		||||
		}
 | 
			
		||||
		if value, ok := c.Resources.Requests[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | 
			
		||||
			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "requests").Key(string(api.ResourceEphemeralStorage)), value.String()))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// duplicate containers[*].env (#86163, #93266, #58477)
 | 
			
		||||
		if len(c.Env) > 1 {
 | 
			
		||||
			items := sets.NewString()
 | 
			
		||||
			for i, item := range c.Env {
 | 
			
		||||
				if items.Has(item.Name) {
 | 
			
		||||
					warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", p.Child("env").Index(i).Child("name"), item.Name))
 | 
			
		||||
				} else {
 | 
			
		||||
					items.Insert(item.Name)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return warnings
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										416
									
								
								pkg/api/pod/warnings_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								pkg/api/pod/warnings_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,416 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2021 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package pod
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	api "k8s.io/kubernetes/pkg/apis/core"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func BenchmarkNoWarnings(b *testing.B) {
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	resources := api.ResourceList{
 | 
			
		||||
		api.ResourceCPU:              resource.MustParse("100m"),
 | 
			
		||||
		api.ResourceMemory:           resource.MustParse("4M"),
 | 
			
		||||
		api.ResourceEphemeralStorage: resource.MustParse("4G"),
 | 
			
		||||
	}
 | 
			
		||||
	env := []api.EnvVar{
 | 
			
		||||
		{Name: "a"},
 | 
			
		||||
		{Name: "b"},
 | 
			
		||||
	}
 | 
			
		||||
	pod := &api.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Annotations: map[string]string{`foo`: `bar`},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: api.PodSpec{
 | 
			
		||||
			NodeSelector: map[string]string{"foo": "bar", "baz": "quux"},
 | 
			
		||||
			Affinity: &api.Affinity{
 | 
			
		||||
				NodeAffinity: &api.NodeAffinity{
 | 
			
		||||
					RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
 | 
			
		||||
						NodeSelectorTerms: []api.NodeSelectorTerm{
 | 
			
		||||
							{MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{
 | 
			
		||||
						{Preference: api.NodeSelectorTerm{MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}}},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			TopologySpreadConstraints: []api.TopologySpreadConstraint{
 | 
			
		||||
				{TopologyKey: `foo`},
 | 
			
		||||
			},
 | 
			
		||||
			HostAliases: []api.HostAlias{
 | 
			
		||||
				{IP: "1.1.1.1"},
 | 
			
		||||
				{IP: "2.2.2.2"},
 | 
			
		||||
			},
 | 
			
		||||
			ImagePullSecrets: []api.LocalObjectReference{
 | 
			
		||||
				{Name: "secret1"},
 | 
			
		||||
				{Name: "secret2"},
 | 
			
		||||
			},
 | 
			
		||||
			InitContainers: []api.Container{
 | 
			
		||||
				{Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
				{Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
			},
 | 
			
		||||
			Containers: []api.Container{
 | 
			
		||||
				{Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
				{Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
			},
 | 
			
		||||
			Overhead: resources,
 | 
			
		||||
			Volumes: []api.Volume{
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	oldPod := &api.Pod{}
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		w := GetWarningsForPod(ctx, pod, oldPod)
 | 
			
		||||
		if len(w) > 0 {
 | 
			
		||||
			b.Fatalf("expected 0 warnings, got %q", w)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkWarnings(b *testing.B) {
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	resources := api.ResourceList{
 | 
			
		||||
		api.ResourceCPU:              resource.MustParse("100m"),
 | 
			
		||||
		api.ResourceMemory:           resource.MustParse("4m"),
 | 
			
		||||
		api.ResourceEphemeralStorage: resource.MustParse("4m"),
 | 
			
		||||
	}
 | 
			
		||||
	env := []api.EnvVar{
 | 
			
		||||
		{Name: "a"},
 | 
			
		||||
		{Name: "a"},
 | 
			
		||||
	}
 | 
			
		||||
	pod := &api.Pod{
 | 
			
		||||
		Spec: api.PodSpec{
 | 
			
		||||
			HostAliases: []api.HostAlias{
 | 
			
		||||
				{IP: "1.1.1.1"},
 | 
			
		||||
				{IP: "1.1.1.1"},
 | 
			
		||||
			},
 | 
			
		||||
			ImagePullSecrets: []api.LocalObjectReference{
 | 
			
		||||
				{Name: "secret1"},
 | 
			
		||||
				{Name: "secret1"},
 | 
			
		||||
				{Name: ""},
 | 
			
		||||
			},
 | 
			
		||||
			InitContainers: []api.Container{
 | 
			
		||||
				{Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
				{Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
			},
 | 
			
		||||
			Containers: []api.Container{
 | 
			
		||||
				{Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
				{Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
 | 
			
		||||
			},
 | 
			
		||||
			Overhead: resources,
 | 
			
		||||
			Volumes: []api.Volume{
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	oldPod := &api.Pod{}
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		GetWarningsForPod(ctx, pod, oldPod)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWarnings(t *testing.T) {
 | 
			
		||||
	resources := api.ResourceList{
 | 
			
		||||
		api.ResourceCPU:              resource.MustParse("100m"),
 | 
			
		||||
		api.ResourceMemory:           resource.MustParse("4m"),
 | 
			
		||||
		api.ResourceEphemeralStorage: resource.MustParse("4m"),
 | 
			
		||||
	}
 | 
			
		||||
	testcases := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		template *api.PodTemplateSpec
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:     "null",
 | 
			
		||||
			template: nil,
 | 
			
		||||
			expected: nil,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "photon",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				Volumes: []api.Volume{
 | 
			
		||||
					{Name: "p", VolumeSource: api.VolumeSource{PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{}}},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{`spec.volumes[0].photonPersistentDisk: non-functional in v1.16+`},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "duplicate hostAlias",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				HostAliases: []api.HostAlias{
 | 
			
		||||
					{IP: "1.1.1.1"},
 | 
			
		||||
					{IP: "1.1.1.1"},
 | 
			
		||||
					{IP: "1.1.1.1"},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.hostAliases[1].ip: duplicate ip "1.1.1.1"`,
 | 
			
		||||
				`spec.hostAliases[2].ip: duplicate ip "1.1.1.1"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "duplicate imagePullSecret",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				ImagePullSecrets: []api.LocalObjectReference{
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.imagePullSecrets[1].name: duplicate name "a"`,
 | 
			
		||||
				`spec.imagePullSecrets[2].name: duplicate name "a"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "empty imagePullSecret",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				ImagePullSecrets: []api.LocalObjectReference{
 | 
			
		||||
					{Name: ""},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.imagePullSecrets[0].name: invalid empty name ""`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "duplicate volume",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				Volumes: []api.Volume{
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.volumes[1].name: duplicate name "a"`,
 | 
			
		||||
				`spec.volumes[2].name: duplicate name "a"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "duplicate env",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				InitContainers: []api.Container{{Env: []api.EnvVar{
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
					{Name: "a"},
 | 
			
		||||
				}}},
 | 
			
		||||
				Containers: []api.Container{{Env: []api.EnvVar{
 | 
			
		||||
					{Name: "b"},
 | 
			
		||||
					{Name: "b"},
 | 
			
		||||
					{Name: "b"},
 | 
			
		||||
				}}},
 | 
			
		||||
			}},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.initContainers[0].env[1].name: duplicate name "a"`,
 | 
			
		||||
				`spec.initContainers[0].env[2].name: duplicate name "a"`,
 | 
			
		||||
				`spec.containers[0].env[1].name: duplicate name "b"`,
 | 
			
		||||
				`spec.containers[0].env[2].name: duplicate name "b"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "fractional resources",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				InitContainers: []api.Container{{
 | 
			
		||||
					Resources: api.ResourceRequirements{Requests: resources, Limits: resources},
 | 
			
		||||
				}},
 | 
			
		||||
				Containers: []api.Container{{
 | 
			
		||||
					Resources: api.ResourceRequirements{Requests: resources, Limits: resources},
 | 
			
		||||
				}},
 | 
			
		||||
				Overhead: resources,
 | 
			
		||||
			}},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.initContainers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.initContainers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.initContainers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.initContainers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.containers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.containers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.containers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.containers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.overhead[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
				`spec.overhead[memory]: fractional byte value "4m" is invalid, must be an integer`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "node labels in nodeSelector",
 | 
			
		||||
			template: &api.PodTemplateSpec{Spec: api.PodSpec{
 | 
			
		||||
				NodeSelector: map[string]string{
 | 
			
		||||
					`beta.kubernetes.io/arch`:                  `true`,
 | 
			
		||||
					`beta.kubernetes.io/os`:                    `true`,
 | 
			
		||||
					`failure-domain.beta.kubernetes.io/region`: `true`,
 | 
			
		||||
					`failure-domain.beta.kubernetes.io/zone`:   `true`,
 | 
			
		||||
					`beta.kubernetes.io/instance-type`:         `true`,
 | 
			
		||||
				},
 | 
			
		||||
			}},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.nodeSelector[beta.kubernetes.io/arch]: deprecated since v1.14; use "kubernetes.io/arch" instead`,
 | 
			
		||||
				`spec.nodeSelector[beta.kubernetes.io/instance-type]: deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
 | 
			
		||||
				`spec.nodeSelector[beta.kubernetes.io/os]: deprecated since v1.14; use "kubernetes.io/os" instead`,
 | 
			
		||||
				`spec.nodeSelector[failure-domain.beta.kubernetes.io/region]: deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
 | 
			
		||||
				`spec.nodeSelector[failure-domain.beta.kubernetes.io/zone]: deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "node labels in affinity requiredDuringSchedulingIgnoredDuringExecution",
 | 
			
		||||
			template: &api.PodTemplateSpec{
 | 
			
		||||
				Spec: api.PodSpec{
 | 
			
		||||
					Affinity: &api.Affinity{
 | 
			
		||||
						NodeAffinity: &api.NodeAffinity{
 | 
			
		||||
							RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
 | 
			
		||||
								NodeSelectorTerms: []api.NodeSelectorTerm{
 | 
			
		||||
									{
 | 
			
		||||
										MatchExpressions: []api.NodeSelectorRequirement{
 | 
			
		||||
											{Key: `foo`},
 | 
			
		||||
											{Key: `beta.kubernetes.io/arch`},
 | 
			
		||||
											{Key: `beta.kubernetes.io/os`},
 | 
			
		||||
											{Key: `failure-domain.beta.kubernetes.io/region`},
 | 
			
		||||
											{Key: `failure-domain.beta.kubernetes.io/zone`},
 | 
			
		||||
											{Key: `beta.kubernetes.io/instance-type`},
 | 
			
		||||
										},
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[3].key: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[4].key: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[5].key: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "node labels in affinity preferredDuringSchedulingIgnoredDuringExecution",
 | 
			
		||||
			template: &api.PodTemplateSpec{
 | 
			
		||||
				Spec: api.PodSpec{
 | 
			
		||||
					Affinity: &api.Affinity{
 | 
			
		||||
						NodeAffinity: &api.NodeAffinity{
 | 
			
		||||
							PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{
 | 
			
		||||
								{
 | 
			
		||||
									Preference: api.NodeSelectorTerm{
 | 
			
		||||
										MatchExpressions: []api.NodeSelectorRequirement{
 | 
			
		||||
											{Key: `foo`},
 | 
			
		||||
											{Key: `beta.kubernetes.io/arch`},
 | 
			
		||||
											{Key: `beta.kubernetes.io/os`},
 | 
			
		||||
											{Key: `failure-domain.beta.kubernetes.io/region`},
 | 
			
		||||
											{Key: `failure-domain.beta.kubernetes.io/zone`},
 | 
			
		||||
											{Key: `beta.kubernetes.io/instance-type`},
 | 
			
		||||
										},
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[3].key: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[4].key: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
 | 
			
		||||
				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[5].key: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "node labels in topologySpreadConstraints",
 | 
			
		||||
			template: &api.PodTemplateSpec{
 | 
			
		||||
				Spec: api.PodSpec{
 | 
			
		||||
					TopologySpreadConstraints: []api.TopologySpreadConstraint{
 | 
			
		||||
						{TopologyKey: `foo`},
 | 
			
		||||
						{TopologyKey: `beta.kubernetes.io/arch`},
 | 
			
		||||
						{TopologyKey: `beta.kubernetes.io/os`},
 | 
			
		||||
						{TopologyKey: `failure-domain.beta.kubernetes.io/region`},
 | 
			
		||||
						{TopologyKey: `failure-domain.beta.kubernetes.io/zone`},
 | 
			
		||||
						{TopologyKey: `beta.kubernetes.io/instance-type`},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`spec.topologySpreadConstraints[1].topologyKey: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
 | 
			
		||||
				`spec.topologySpreadConstraints[2].topologyKey: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
 | 
			
		||||
				`spec.topologySpreadConstraints[3].topologyKey: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
 | 
			
		||||
				`spec.topologySpreadConstraints[4].topologyKey: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
 | 
			
		||||
				`spec.topologySpreadConstraints[5].topologyKey: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "annotations",
 | 
			
		||||
			template: &api.PodTemplateSpec{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
 | 
			
		||||
				`foo`: `bar`,
 | 
			
		||||
				`scheduler.alpha.kubernetes.io/critical-pod`:         `true`,
 | 
			
		||||
				`seccomp.security.alpha.kubernetes.io/pod`:           `default`,
 | 
			
		||||
				`container.seccomp.security.alpha.kubernetes.io/foo`: `default`,
 | 
			
		||||
				`security.alpha.kubernetes.io/sysctls`:               `a,b,c`,
 | 
			
		||||
				`security.alpha.kubernetes.io/unsafe-sysctls`:        `d,e,f`,
 | 
			
		||||
			}}},
 | 
			
		||||
			expected: []string{
 | 
			
		||||
				`metadata.annotations[scheduler.alpha.kubernetes.io/critical-pod]: non-functional in v1.16+; use the "priorityClassName" field instead`,
 | 
			
		||||
				`metadata.annotations[seccomp.security.alpha.kubernetes.io/pod]: deprecated since v1.19; use the "seccompProfile" field instead`,
 | 
			
		||||
				`metadata.annotations[container.seccomp.security.alpha.kubernetes.io/foo]: deprecated since v1.19; use the "seccompProfile" field instead`,
 | 
			
		||||
				`metadata.annotations[security.alpha.kubernetes.io/sysctls]: non-functional in v1.11+; use the "sysctls" field instead`,
 | 
			
		||||
				`metadata.annotations[security.alpha.kubernetes.io/unsafe-sysctls]: non-functional in v1.11+; use the "sysctls" field instead`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range testcases {
 | 
			
		||||
		t.Run("podspec_"+tc.name, func(t *testing.T) {
 | 
			
		||||
			actual := sets.NewString(GetWarningsForPodTemplate(context.TODO(), nil, tc.template, &api.PodTemplateSpec{})...)
 | 
			
		||||
			expected := sets.NewString(tc.expected...)
 | 
			
		||||
			for _, missing := range expected.Difference(actual).List() {
 | 
			
		||||
				t.Errorf("missing: %s", missing)
 | 
			
		||||
			}
 | 
			
		||||
			for _, extra := range actual.Difference(expected).List() {
 | 
			
		||||
				t.Errorf("extra: %s", extra)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("pod_"+tc.name, func(t *testing.T) {
 | 
			
		||||
			var pod *api.Pod
 | 
			
		||||
			if tc.template != nil {
 | 
			
		||||
				pod = &api.Pod{
 | 
			
		||||
					ObjectMeta: tc.template.ObjectMeta,
 | 
			
		||||
					Spec:       tc.template.Spec,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			actual := sets.NewString(GetWarningsForPod(context.TODO(), pod, &api.Pod{})...)
 | 
			
		||||
			expected := sets.NewString(tc.expected...)
 | 
			
		||||
			for _, missing := range expected.Difference(actual).List() {
 | 
			
		||||
				t.Errorf("missing: %s", missing)
 | 
			
		||||
			}
 | 
			
		||||
			for _, extra := range actual.Difference(expected).List() {
 | 
			
		||||
				t.Errorf("extra: %s", extra)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user