| /* |
| Copyright 2017 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 ( |
| "fmt" |
| "reflect" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
| |
| v1 "k8s.io/api/core/v1" |
| "k8s.io/apimachinery/pkg/api/resource" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/apimachinery/pkg/util/validation/field" |
| utilfeature "k8s.io/apiserver/pkg/util/feature" |
| featuregatetesting "k8s.io/component-base/featuregate/testing" |
| api "k8s.io/kubernetes/pkg/apis/core" |
| "k8s.io/kubernetes/pkg/features" |
| "k8s.io/utils/pointer" |
| ) |
| |
| func TestVisitContainers(t *testing.T) { |
| setAllFeatureEnabledContainersDuringTest := ContainerType(0) |
| testCases := []struct { |
| desc string |
| spec *api.PodSpec |
| wantContainers []string |
| mask ContainerType |
| }{ |
| { |
| desc: "empty podspec", |
| spec: &api.PodSpec{}, |
| wantContainers: []string{}, |
| mask: AllContainers, |
| }, |
| { |
| desc: "regular containers", |
| spec: &api.PodSpec{ |
| Containers: []api.Container{ |
| {Name: "c1"}, |
| {Name: "c2"}, |
| }, |
| InitContainers: []api.Container{ |
| {Name: "i1"}, |
| {Name: "i2"}, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}}, |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}}, |
| }, |
| }, |
| wantContainers: []string{"c1", "c2"}, |
| mask: Containers, |
| }, |
| { |
| desc: "init containers", |
| spec: &api.PodSpec{ |
| Containers: []api.Container{ |
| {Name: "c1"}, |
| {Name: "c2"}, |
| }, |
| InitContainers: []api.Container{ |
| {Name: "i1"}, |
| {Name: "i2"}, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}}, |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}}, |
| }, |
| }, |
| wantContainers: []string{"i1", "i2"}, |
| mask: InitContainers, |
| }, |
| { |
| desc: "ephemeral containers", |
| spec: &api.PodSpec{ |
| Containers: []api.Container{ |
| {Name: "c1"}, |
| {Name: "c2"}, |
| }, |
| InitContainers: []api.Container{ |
| {Name: "i1"}, |
| {Name: "i2"}, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}}, |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}}, |
| }, |
| }, |
| wantContainers: []string{"e1", "e2"}, |
| mask: EphemeralContainers, |
| }, |
| { |
| desc: "all container types", |
| spec: &api.PodSpec{ |
| Containers: []api.Container{ |
| {Name: "c1"}, |
| {Name: "c2"}, |
| }, |
| InitContainers: []api.Container{ |
| {Name: "i1"}, |
| {Name: "i2"}, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}}, |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}}, |
| }, |
| }, |
| wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"}, |
| mask: AllContainers, |
| }, |
| { |
| desc: "all feature enabled container types with ephemeral containers enabled", |
| spec: &api.PodSpec{ |
| Containers: []api.Container{ |
| {Name: "c1"}, |
| {Name: "c2", SecurityContext: &api.SecurityContext{}}, |
| }, |
| InitContainers: []api.Container{ |
| {Name: "i1"}, |
| {Name: "i2", SecurityContext: &api.SecurityContext{}}, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}}, |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}}, |
| }, |
| }, |
| wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"}, |
| mask: setAllFeatureEnabledContainersDuringTest, |
| }, |
| { |
| desc: "dropping fields", |
| spec: &api.PodSpec{ |
| Containers: []api.Container{ |
| {Name: "c1"}, |
| {Name: "c2", SecurityContext: &api.SecurityContext{}}, |
| }, |
| InitContainers: []api.Container{ |
| {Name: "i1"}, |
| {Name: "i2", SecurityContext: &api.SecurityContext{}}, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}}, |
| {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2", SecurityContext: &api.SecurityContext{}}}, |
| }, |
| }, |
| wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"}, |
| mask: AllContainers, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.desc, func(t *testing.T) { |
| if tc.mask == setAllFeatureEnabledContainersDuringTest { |
| tc.mask = AllFeatureEnabledContainers() |
| } |
| |
| gotContainers := []string{} |
| VisitContainers(tc.spec, tc.mask, func(c *api.Container, containerType ContainerType) bool { |
| gotContainers = append(gotContainers, c.Name) |
| if c.SecurityContext != nil { |
| c.SecurityContext = nil |
| } |
| return true |
| }) |
| if !cmp.Equal(gotContainers, tc.wantContainers) { |
| t.Errorf("VisitContainers() = %+v, want %+v", gotContainers, tc.wantContainers) |
| } |
| for _, c := range tc.spec.Containers { |
| if c.SecurityContext != nil { |
| t.Errorf("VisitContainers() did not drop SecurityContext for container %q", c.Name) |
| } |
| } |
| for _, c := range tc.spec.InitContainers { |
| if c.SecurityContext != nil { |
| t.Errorf("VisitContainers() did not drop SecurityContext for init container %q", c.Name) |
| } |
| } |
| for _, c := range tc.spec.EphemeralContainers { |
| if c.SecurityContext != nil { |
| t.Errorf("VisitContainers() did not drop SecurityContext for ephemeral container %q", c.Name) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestPodSecrets(t *testing.T) { |
| // Stub containing all possible secret references in a pod. |
| // The names of the referenced secrets match struct paths detected by reflection. |
| pod := &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{{ |
| EnvFrom: []api.EnvFromSource{{ |
| SecretRef: &api.SecretEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Containers[*].EnvFrom[*].SecretRef"}}}}, |
| Env: []api.EnvVar{{ |
| ValueFrom: &api.EnvVarSource{ |
| SecretKeyRef: &api.SecretKeySelector{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}}, |
| ImagePullSecrets: []api.LocalObjectReference{{ |
| Name: "Spec.ImagePullSecrets"}}, |
| InitContainers: []api.Container{{ |
| EnvFrom: []api.EnvFromSource{{ |
| SecretRef: &api.SecretEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.InitContainers[*].EnvFrom[*].SecretRef"}}}}, |
| Env: []api.EnvVar{{ |
| ValueFrom: &api.EnvVarSource{ |
| SecretKeyRef: &api.SecretKeySelector{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}}, |
| Volumes: []api.Volume{{ |
| VolumeSource: api.VolumeSource{ |
| AzureFile: &api.AzureFileVolumeSource{ |
| SecretName: "Spec.Volumes[*].VolumeSource.AzureFile.SecretName"}}}, { |
| VolumeSource: api.VolumeSource{ |
| CephFS: &api.CephFSVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.CephFS.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| Cinder: &api.CinderVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.Cinder.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| FlexVolume: &api.FlexVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{{ |
| Secret: &api.SecretProjection{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret"}}}}}}}, { |
| VolumeSource: api.VolumeSource{ |
| RBD: &api.RBDVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.RBD.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| Secret: &api.SecretVolumeSource{ |
| SecretName: "Spec.Volumes[*].VolumeSource.Secret.SecretName"}}}, { |
| VolumeSource: api.VolumeSource{ |
| Secret: &api.SecretVolumeSource{ |
| SecretName: "Spec.Volumes[*].VolumeSource.Secret"}}}, { |
| VolumeSource: api.VolumeSource{ |
| ScaleIO: &api.ScaleIOVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| ISCSI: &api.ISCSIVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| StorageOS: &api.StorageOSVolumeSource{ |
| SecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}, { |
| VolumeSource: api.VolumeSource{ |
| CSI: &api.CSIVolumeSource{ |
| NodePublishSecretRef: &api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}}, |
| EphemeralContainers: []api.EphemeralContainer{{ |
| EphemeralContainerCommon: api.EphemeralContainerCommon{ |
| EnvFrom: []api.EnvFromSource{{ |
| SecretRef: &api.SecretEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef"}}}}, |
| Env: []api.EnvVar{{ |
| ValueFrom: &api.EnvVarSource{ |
| SecretKeyRef: &api.SecretKeySelector{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef"}}}}}}}}, |
| }, |
| } |
| extractedNames := sets.New[string]() |
| VisitPodSecretNames(pod, func(name string) bool { |
| extractedNames.Insert(name) |
| return true |
| }, AllContainers) |
| |
| // excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects |
| excludedSecretPaths := sets.New[string]( |
| "Spec.Volumes[*].VolumeSource.CephFS.SecretFile", |
| ) |
| // expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects. |
| // every path here should be represented as an example in the Pod stub above, with the secret name set to the path. |
| expectedSecretPaths := sets.New[string]( |
| "Spec.Containers[*].EnvFrom[*].SecretRef", |
| "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef", |
| "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef", |
| "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef", |
| "Spec.ImagePullSecrets", |
| "Spec.InitContainers[*].EnvFrom[*].SecretRef", |
| "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef", |
| "Spec.Volumes[*].VolumeSource.AzureFile.SecretName", |
| "Spec.Volumes[*].VolumeSource.CephFS.SecretRef", |
| "Spec.Volumes[*].VolumeSource.Cinder.SecretRef", |
| "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef", |
| "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret", |
| "Spec.Volumes[*].VolumeSource.RBD.SecretRef", |
| "Spec.Volumes[*].VolumeSource.Secret", |
| "Spec.Volumes[*].VolumeSource.Secret.SecretName", |
| "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef", |
| "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef", |
| "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef", |
| "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef", |
| ) |
| secretPaths := collectResourcePaths(t, "secret", nil, "", reflect.TypeOf(&api.Pod{})) |
| secretPaths = secretPaths.Difference(excludedSecretPaths) |
| if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 { |
| t.Logf("Missing expected secret paths:\n%s", strings.Join(sets.List[string](missingPaths), "\n")) |
| t.Error("Missing expected secret paths. Verify VisitPodSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths") |
| } |
| if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 { |
| t.Logf("Extra secret paths:\n%s", strings.Join(sets.List[string](extraPaths), "\n")) |
| t.Error("Extra fields with 'secret' in the name found. Verify VisitPodSecretNames() is including these fields if appropriate, then correct expectedSecretPaths") |
| } |
| |
| if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 { |
| t.Logf("Missing expected secret names:\n%s", strings.Join(sets.List[string](missingNames), "\n")) |
| t.Error("Missing expected secret names. Verify the pod stub above includes these references, then verify VisitPodSecretNames() is correctly finding the missing names") |
| } |
| if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 { |
| t.Logf("Extra secret names:\n%s", strings.Join(sets.List[string](extraNames), "\n")) |
| t.Error("Extra secret names extracted. Verify VisitPodSecretNames() is correctly extracting secret names") |
| } |
| |
| // emptyPod is a stub containing empty object names |
| emptyPod := &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{{ |
| EnvFrom: []api.EnvFromSource{{ |
| SecretRef: &api.SecretEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: ""}}}}}}, |
| }, |
| } |
| VisitPodSecretNames(emptyPod, func(name string) bool { |
| t.Fatalf("expected no empty names collected, got %q", name) |
| return false |
| }, AllContainers) |
| } |
| |
| // collectResourcePaths traverses the object, computing all the struct paths that lead to fields with resourcename in the name. |
| func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, name string, tp reflect.Type) sets.Set[string] { |
| resourcename = strings.ToLower(resourcename) |
| resourcePaths := sets.New[string]() |
| |
| if tp.Kind() == reflect.Pointer { |
| resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path, name, tp.Elem()))...) |
| return resourcePaths |
| } |
| |
| if strings.Contains(strings.ToLower(name), resourcename) { |
| resourcePaths.Insert(path.String()) |
| } |
| |
| switch tp.Kind() { |
| case reflect.Pointer: |
| resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path, name, tp.Elem()))...) |
| case reflect.Struct: |
| // ObjectMeta is generic and therefore should never have a field with a specific resource's name; |
| // it contains cycles so it's easiest to just skip it. |
| if name == "ObjectMeta" { |
| break |
| } |
| for i := 0; i < tp.NumField(); i++ { |
| field := tp.Field(i) |
| resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type))...) |
| } |
| case reflect.Interface: |
| t.Errorf("cannot find %s fields in interface{} field %s", resourcename, path.String()) |
| case reflect.Map: |
| resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()))...) |
| case reflect.Slice: |
| resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()))...) |
| default: |
| // all primitive types |
| } |
| |
| return resourcePaths |
| } |
| |
| func TestPodConfigmaps(t *testing.T) { |
| // Stub containing all possible ConfigMap references in a pod. |
| // The names of the referenced ConfigMaps match struct paths detected by reflection. |
| pod := &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{{ |
| EnvFrom: []api.EnvFromSource{{ |
| ConfigMapRef: &api.ConfigMapEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Containers[*].EnvFrom[*].ConfigMapRef"}}}}, |
| Env: []api.EnvVar{{ |
| ValueFrom: &api.EnvVarSource{ |
| ConfigMapKeyRef: &api.ConfigMapKeySelector{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}, |
| EphemeralContainers: []api.EphemeralContainer{{ |
| EphemeralContainerCommon: api.EphemeralContainerCommon{ |
| EnvFrom: []api.EnvFromSource{{ |
| ConfigMapRef: &api.ConfigMapEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef"}}}}, |
| Env: []api.EnvVar{{ |
| ValueFrom: &api.EnvVarSource{ |
| ConfigMapKeyRef: &api.ConfigMapKeySelector{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}}, |
| InitContainers: []api.Container{{ |
| EnvFrom: []api.EnvFromSource{{ |
| ConfigMapRef: &api.ConfigMapEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef"}}}}, |
| Env: []api.EnvVar{{ |
| ValueFrom: &api.EnvVarSource{ |
| ConfigMapKeyRef: &api.ConfigMapKeySelector{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}, |
| Volumes: []api.Volume{{ |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{{ |
| ConfigMap: &api.ConfigMapProjection{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap"}}}}}}}, { |
| VolumeSource: api.VolumeSource{ |
| ConfigMap: &api.ConfigMapVolumeSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: "Spec.Volumes[*].VolumeSource.ConfigMap"}}}}}, |
| }, |
| } |
| extractedNames := sets.New[string]() |
| VisitPodConfigmapNames(pod, func(name string) bool { |
| extractedNames.Insert(name) |
| return true |
| }, AllContainers) |
| |
| // expectedPaths holds struct paths to fields with "ConfigMap" in the name that are references to ConfigMap API objects. |
| // every path here should be represented as an example in the Pod stub above, with the ConfigMap name set to the path. |
| expectedPaths := sets.New[string]( |
| "Spec.Containers[*].EnvFrom[*].ConfigMapRef", |
| "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef", |
| "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef", |
| "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef", |
| "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef", |
| "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef", |
| "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap", |
| "Spec.Volumes[*].VolumeSource.ConfigMap", |
| ) |
| collectPaths := collectResourcePaths(t, "ConfigMap", nil, "", reflect.TypeOf(&api.Pod{})) |
| if missingPaths := expectedPaths.Difference(collectPaths); len(missingPaths) > 0 { |
| t.Logf("Missing expected paths:\n%s", strings.Join(sets.List[string](missingPaths), "\n")) |
| t.Error("Missing expected paths. Verify VisitPodConfigmapNames() is correctly finding the missing paths, then correct expectedPaths") |
| } |
| if extraPaths := collectPaths.Difference(expectedPaths); len(extraPaths) > 0 { |
| t.Logf("Extra paths:\n%s", strings.Join(sets.List[string](extraPaths), "\n")) |
| t.Error("Extra fields with resource in the name found. Verify VisitPodConfigmapNames() is including these fields if appropriate, then correct expectedPaths") |
| } |
| |
| if missingNames := expectedPaths.Difference(extractedNames); len(missingNames) > 0 { |
| t.Logf("Missing expected names:\n%s", strings.Join(sets.List[string](missingNames), "\n")) |
| t.Error("Missing expected names. Verify the pod stub above includes these references, then verify VisitPodConfigmapNames() is correctly finding the missing names") |
| } |
| if extraNames := extractedNames.Difference(expectedPaths); len(extraNames) > 0 { |
| t.Logf("Extra names:\n%s", strings.Join(sets.List[string](extraNames), "\n")) |
| t.Error("Extra names extracted. Verify VisitPodConfigmapNames() is correctly extracting resource names") |
| } |
| |
| // emptyPod is a stub containing empty object names |
| emptyPod := &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{{ |
| EnvFrom: []api.EnvFromSource{{ |
| ConfigMapRef: &api.ConfigMapEnvSource{ |
| LocalObjectReference: api.LocalObjectReference{ |
| Name: ""}}}}}}, |
| }, |
| } |
| VisitPodConfigmapNames(emptyPod, func(name string) bool { |
| t.Fatalf("expected no empty names collected, got %q", name) |
| return false |
| }, AllContainers) |
| } |
| |
| func TestDropFSGroupFields(t *testing.T) { |
| nofsGroupPod := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "container1", |
| Image: "testimage", |
| }, |
| }, |
| }, |
| } |
| } |
| |
| var podFSGroup int64 = 100 |
| changePolicy := api.FSGroupChangeAlways |
| |
| fsGroupPod := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "container1", |
| Image: "testimage", |
| }, |
| }, |
| SecurityContext: &api.PodSecurityContext{ |
| FSGroup: &podFSGroup, |
| FSGroupChangePolicy: &changePolicy, |
| }, |
| }, |
| } |
| } |
| podInfos := []struct { |
| description string |
| newPodHasFSGroupChangePolicy bool |
| pod func() *api.Pod |
| expectPolicyInPod bool |
| }{ |
| { |
| description: "oldPod.FSGroupChangePolicy=nil, feature=true, newPod.FSGroupChangePolicy=true", |
| pod: nofsGroupPod, |
| newPodHasFSGroupChangePolicy: true, |
| expectPolicyInPod: true, |
| }, |
| { |
| description: "oldPod=nil, feature=true, newPod.FSGroupChangePolicy=true", |
| pod: func() *api.Pod { return nil }, |
| newPodHasFSGroupChangePolicy: true, |
| expectPolicyInPod: true, |
| }, |
| { |
| description: "oldPod.FSGroupChangePolicy=true, feature=true, newPod.FSGroupChangePolicy=false", |
| pod: fsGroupPod, |
| newPodHasFSGroupChangePolicy: false, |
| expectPolicyInPod: false, |
| }, |
| } |
| for _, podInfo := range podInfos { |
| t.Run(podInfo.description, func(t *testing.T) { |
| oldPod := podInfo.pod() |
| newPod := oldPod.DeepCopy() |
| if oldPod == nil && podInfo.newPodHasFSGroupChangePolicy { |
| newPod = fsGroupPod() |
| } |
| |
| if oldPod != nil { |
| if podInfo.newPodHasFSGroupChangePolicy { |
| newPod.Spec.SecurityContext = &api.PodSecurityContext{ |
| FSGroup: &podFSGroup, |
| FSGroupChangePolicy: &changePolicy, |
| } |
| } else { |
| newPod.Spec.SecurityContext = &api.PodSecurityContext{} |
| } |
| } |
| DropDisabledPodFields(newPod, oldPod) |
| |
| if podInfo.expectPolicyInPod { |
| secContext := newPod.Spec.SecurityContext |
| if secContext == nil || secContext.FSGroupChangePolicy == nil { |
| t.Errorf("for %s, expected fsGroupChangepolicy found none", podInfo.description) |
| } |
| } else { |
| secContext := newPod.Spec.SecurityContext |
| if secContext != nil && secContext.FSGroupChangePolicy != nil { |
| t.Errorf("for %s, unexpected fsGroupChangepolicy set", podInfo.description) |
| } |
| } |
| }) |
| } |
| |
| } |
| |
| func TestDropProcMount(t *testing.T) { |
| procMount := api.UnmaskedProcMount |
| defaultProcMount := api.DefaultProcMount |
| podWithProcMount := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| RestartPolicy: api.RestartPolicyNever, |
| Containers: []api.Container{{Name: "container1", Image: "testimage", SecurityContext: &api.SecurityContext{ProcMount: &procMount}}}, |
| InitContainers: []api.Container{{Name: "container1", Image: "testimage", SecurityContext: &api.SecurityContext{ProcMount: &procMount}}}, |
| }, |
| } |
| } |
| podWithDefaultProcMount := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| RestartPolicy: api.RestartPolicyNever, |
| Containers: []api.Container{{Name: "container1", Image: "testimage", SecurityContext: &api.SecurityContext{ProcMount: &defaultProcMount}}}, |
| InitContainers: []api.Container{{Name: "container1", Image: "testimage", SecurityContext: &api.SecurityContext{ProcMount: &defaultProcMount}}}, |
| }, |
| } |
| } |
| podWithoutProcMount := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| RestartPolicy: api.RestartPolicyNever, |
| Containers: []api.Container{{Name: "container1", Image: "testimage", SecurityContext: &api.SecurityContext{ProcMount: nil}}}, |
| InitContainers: []api.Container{{Name: "container1", Image: "testimage", SecurityContext: &api.SecurityContext{ProcMount: nil}}}, |
| }, |
| } |
| } |
| |
| podInfo := []struct { |
| description string |
| hasProcMount bool |
| pod func() *api.Pod |
| }{ |
| { |
| description: "has ProcMount", |
| hasProcMount: true, |
| pod: podWithProcMount, |
| }, |
| { |
| description: "has default ProcMount", |
| hasProcMount: false, |
| pod: podWithDefaultProcMount, |
| }, |
| { |
| description: "does not have ProcMount", |
| hasProcMount: false, |
| pod: podWithoutProcMount, |
| }, |
| { |
| description: "is nil", |
| hasProcMount: false, |
| pod: func() *api.Pod { return nil }, |
| }, |
| } |
| |
| for _, enabled := range []bool{true, false} { |
| for _, oldPodInfo := range podInfo { |
| for _, newPodInfo := range podInfo { |
| oldPodHasProcMount, oldPod := oldPodInfo.hasProcMount, oldPodInfo.pod() |
| newPodHasProcMount, newPod := newPodInfo.hasProcMount, newPodInfo.pod() |
| if newPod == nil { |
| continue |
| } |
| |
| t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ProcMountType, enabled)() |
| |
| var oldPodSpec *api.PodSpec |
| if oldPod != nil { |
| oldPodSpec = &oldPod.Spec |
| } |
| dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil) |
| |
| // old pod should never be changed |
| if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) { |
| t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod())) |
| } |
| |
| switch { |
| case enabled || oldPodHasProcMount: |
| // new pod should not be changed if the feature is enabled, or if the old pod had ProcMount |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| case newPodHasProcMount: |
| // new pod should be changed |
| if reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod was not changed") |
| } |
| // new pod should not have ProcMount |
| if procMountInUse(&newPod.Spec) { |
| t.Errorf("new pod had ProcMount: %#v", &newPod.Spec) |
| } |
| default: |
| // new pod should not need to be changed |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| } |
| }) |
| } |
| } |
| } |
| } |
| |
| func TestDropAppArmor(t *testing.T) { |
| tests := []struct { |
| description string |
| hasAnnotations bool |
| hasFields bool |
| pod api.Pod |
| }{{ |
| description: "with AppArmor Annotations", |
| hasAnnotations: true, |
| pod: api.Pod{ |
| ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}}, |
| Spec: api.PodSpec{}, |
| }, |
| }, { |
| description: "with AppArmor Annotations & fields", |
| hasAnnotations: true, |
| hasFields: true, |
| pod: api.Pod{ |
| ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}}, |
| Spec: api.PodSpec{ |
| SecurityContext: &api.PodSecurityContext{ |
| AppArmorProfile: &api.AppArmorProfile{ |
| Type: api.AppArmorProfileTypeRuntimeDefault, |
| }, |
| }, |
| }, |
| }, |
| }, { |
| description: "with pod AppArmor profile", |
| hasFields: true, |
| pod: api.Pod{ |
| ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, |
| Spec: api.PodSpec{ |
| SecurityContext: &api.PodSecurityContext{ |
| AppArmorProfile: &api.AppArmorProfile{ |
| Type: api.AppArmorProfileTypeRuntimeDefault, |
| }, |
| }, |
| }, |
| }, |
| }, { |
| description: "with container AppArmor profile", |
| hasFields: true, |
| pod: api.Pod{ |
| ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, |
| Spec: api.PodSpec{ |
| Containers: []api.Container{{ |
| SecurityContext: &api.SecurityContext{ |
| AppArmorProfile: &api.AppArmorProfile{ |
| Type: api.AppArmorProfileTypeRuntimeDefault, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, { |
| description: "without AppArmor", |
| pod: api.Pod{ |
| ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, |
| Spec: api.PodSpec{}, |
| }, |
| }} |
| |
| for _, test := range tests { |
| for _, enabled := range []bool{true, false} { |
| for _, fieldsEnabled := range []bool{true, false} { |
| t.Run(fmt.Sprintf("%v/enabled=%v/fields=%v", test.description, enabled, fieldsEnabled), func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)() |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmorFields, fieldsEnabled)() |
| |
| newPod := test.pod.DeepCopy() |
| |
| if hasAnnotations := appArmorAnnotationsInUse(newPod.Annotations); hasAnnotations != test.hasAnnotations { |
| t.Errorf("appArmorAnnotationsInUse does not match expectation: %t != %t", hasAnnotations, test.hasAnnotations) |
| } |
| if hasFields := appArmorFieldsInUse(&newPod.Spec); hasFields != test.hasFields { |
| t.Errorf("appArmorFieldsInUse does not match expectation: %t != %t", hasFields, test.hasFields) |
| } |
| |
| DropDisabledPodFields(newPod, newPod) |
| require.Equal(t, &test.pod, newPod, "unchanged pod should never be mutated") |
| |
| DropDisabledPodFields(newPod, nil) |
| |
| if enabled && fieldsEnabled { |
| assert.Equal(t, &test.pod, newPod, "pod should not be mutated when both feature gates are enabled") |
| return |
| } |
| |
| expectAnnotations := test.hasAnnotations && enabled |
| assert.Equal(t, expectAnnotations, appArmorAnnotationsInUse(newPod.Annotations), "AppArmor annotations expectation") |
| if expectAnnotations == test.hasAnnotations { |
| assert.Equal(t, test.pod.Annotations, newPod.Annotations, "annotations should not be mutated") |
| } |
| |
| expectFields := test.hasFields && enabled && fieldsEnabled |
| assert.Equal(t, expectFields, appArmorFieldsInUse(&newPod.Spec), "AppArmor fields expectation") |
| if expectFields == test.hasFields { |
| assert.Equal(t, &test.pod.Spec, &newPod.Spec, "PodSpec should not be mutated") |
| } |
| }) |
| } |
| } |
| } |
| } |
| |
| func TestDropDynamicResourceAllocation(t *testing.T) { |
| resourceClaimName := "external-claim" |
| |
| podWithClaims := &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Resources: api.ResourceRequirements{ |
| Claims: []api.ResourceClaim{{Name: "my-claim"}}, |
| }, |
| }, |
| }, |
| InitContainers: []api.Container{ |
| { |
| Resources: api.ResourceRequirements{ |
| Claims: []api.ResourceClaim{{Name: "my-claim"}}, |
| }, |
| }, |
| }, |
| EphemeralContainers: []api.EphemeralContainer{ |
| { |
| EphemeralContainerCommon: api.EphemeralContainerCommon{ |
| Resources: api.ResourceRequirements{ |
| Claims: []api.ResourceClaim{{Name: "my-claim"}}, |
| }, |
| }, |
| }, |
| }, |
| ResourceClaims: []api.PodResourceClaim{ |
| { |
| Name: "my-claim", |
| Source: api.ClaimSource{ |
| ResourceClaimName: &resourceClaimName, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ResourceClaimStatuses: []api.PodResourceClaimStatus{ |
| {Name: "my-claim", ResourceClaimName: pointer.String("pod-my-claim")}, |
| }, |
| }, |
| } |
| podWithoutClaims := &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{{}}, |
| InitContainers: []api.Container{{}}, |
| EphemeralContainers: []api.EphemeralContainer{{}}, |
| }, |
| } |
| |
| var noPod *api.Pod |
| |
| testcases := []struct { |
| description string |
| enabled bool |
| oldPod *api.Pod |
| newPod *api.Pod |
| wantPod *api.Pod |
| }{ |
| { |
| description: "old with claims / new with claims / disabled", |
| oldPod: podWithClaims, |
| newPod: podWithClaims, |
| wantPod: podWithClaims, |
| }, |
| { |
| description: "old without claims / new with claims / disabled", |
| oldPod: podWithoutClaims, |
| newPod: podWithClaims, |
| wantPod: podWithoutClaims, |
| }, |
| { |
| description: "no old pod/ new with claims / disabled", |
| oldPod: noPod, |
| newPod: podWithClaims, |
| wantPod: podWithoutClaims, |
| }, |
| |
| { |
| description: "old with claims / new without claims / disabled", |
| oldPod: podWithClaims, |
| newPod: podWithoutClaims, |
| wantPod: podWithoutClaims, |
| }, |
| { |
| description: "old without claims / new without claims / disabled", |
| oldPod: podWithoutClaims, |
| newPod: podWithoutClaims, |
| wantPod: podWithoutClaims, |
| }, |
| { |
| description: "no old pod/ new without claims / disabled", |
| oldPod: noPod, |
| newPod: podWithoutClaims, |
| wantPod: podWithoutClaims, |
| }, |
| |
| { |
| description: "old with claims / new with claims / enabled", |
| enabled: true, |
| oldPod: podWithClaims, |
| newPod: podWithClaims, |
| wantPod: podWithClaims, |
| }, |
| { |
| description: "old without claims / new with claims / enabled", |
| enabled: true, |
| oldPod: podWithoutClaims, |
| newPod: podWithClaims, |
| wantPod: podWithClaims, |
| }, |
| { |
| description: "no old pod/ new with claims / enabled", |
| enabled: true, |
| oldPod: noPod, |
| newPod: podWithClaims, |
| wantPod: podWithClaims, |
| }, |
| |
| { |
| description: "old with claims / new without claims / enabled", |
| enabled: true, |
| oldPod: podWithClaims, |
| newPod: podWithoutClaims, |
| wantPod: podWithoutClaims, |
| }, |
| { |
| description: "old without claims / new without claims / enabled", |
| enabled: true, |
| oldPod: podWithoutClaims, |
| newPod: podWithoutClaims, |
| wantPod: podWithoutClaims, |
| }, |
| { |
| description: "no old pod/ new without claims / enabled", |
| enabled: true, |
| oldPod: noPod, |
| newPod: podWithoutClaims, |
| wantPod: podWithoutClaims, |
| }, |
| } |
| |
| for _, tc := range testcases { |
| t.Run(tc.description, func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicResourceAllocation, tc.enabled)() |
| |
| oldPod := tc.oldPod.DeepCopy() |
| newPod := tc.newPod.DeepCopy() |
| wantPod := tc.wantPod |
| DropDisabledPodFields(newPod, oldPod) |
| |
| // old pod should never be changed |
| if diff := cmp.Diff(oldPod, tc.oldPod); diff != "" { |
| t.Errorf("old pod changed: %s", diff) |
| } |
| |
| if diff := cmp.Diff(wantPod, newPod); diff != "" { |
| t.Errorf("new pod changed (- want, + got): %s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestValidatePodDeletionCostOption(t *testing.T) { |
| testCases := []struct { |
| name string |
| oldPodMeta *metav1.ObjectMeta |
| featureEnabled bool |
| wantAllowInvalidPodDeletionCost bool |
| }{ |
| { |
| name: "CreateFeatureEnabled", |
| featureEnabled: true, |
| wantAllowInvalidPodDeletionCost: false, |
| }, |
| { |
| name: "CreateFeatureDisabled", |
| featureEnabled: false, |
| wantAllowInvalidPodDeletionCost: true, |
| }, |
| { |
| name: "UpdateFeatureDisabled", |
| oldPodMeta: &metav1.ObjectMeta{Annotations: map[string]string{api.PodDeletionCost: "100"}}, |
| featureEnabled: false, |
| wantAllowInvalidPodDeletionCost: true, |
| }, |
| { |
| name: "UpdateFeatureEnabledValidOldValue", |
| oldPodMeta: &metav1.ObjectMeta{Annotations: map[string]string{api.PodDeletionCost: "100"}}, |
| featureEnabled: true, |
| wantAllowInvalidPodDeletionCost: false, |
| }, |
| { |
| name: "UpdateFeatureEnabledValidOldValue", |
| oldPodMeta: &metav1.ObjectMeta{Annotations: map[string]string{api.PodDeletionCost: "invalid-value"}}, |
| featureEnabled: true, |
| wantAllowInvalidPodDeletionCost: true, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDeletionCost, tc.featureEnabled)() |
| // The new pod doesn't impact the outcome. |
| gotOptions := GetValidationOptionsFromPodSpecAndMeta(nil, nil, nil, tc.oldPodMeta) |
| if tc.wantAllowInvalidPodDeletionCost != gotOptions.AllowInvalidPodDeletionCost { |
| t.Errorf("unexpected diff, want: %v, got: %v", tc.wantAllowInvalidPodDeletionCost, gotOptions.AllowInvalidPodDeletionCost) |
| } |
| }) |
| } |
| } |
| |
| func TestDropDisabledPodStatusFields(t *testing.T) { |
| podWithHostIPs := func() *api.PodStatus { |
| return &api.PodStatus{ |
| HostIPs: makeHostIPs("10.0.0.1", "fd00:10::1"), |
| } |
| } |
| |
| podWithoutHostIPs := func() *api.PodStatus { |
| return &api.PodStatus{ |
| HostIPs: nil, |
| } |
| } |
| |
| tests := []struct { |
| name string |
| podStatus *api.PodStatus |
| oldPodStatus *api.PodStatus |
| wantPodStatus *api.PodStatus |
| }{ |
| { |
| name: "old=without, new=without", |
| oldPodStatus: podWithoutHostIPs(), |
| podStatus: podWithoutHostIPs(), |
| |
| wantPodStatus: podWithoutHostIPs(), |
| }, |
| { |
| name: "old=without, new=with", |
| oldPodStatus: podWithoutHostIPs(), |
| podStatus: podWithHostIPs(), |
| |
| wantPodStatus: podWithHostIPs(), |
| }, |
| { |
| name: "old=with, new=without", |
| oldPodStatus: podWithHostIPs(), |
| podStatus: podWithoutHostIPs(), |
| |
| wantPodStatus: podWithoutHostIPs(), |
| }, |
| { |
| name: "old=with, new=with", |
| oldPodStatus: podWithHostIPs(), |
| podStatus: podWithHostIPs(), |
| |
| wantPodStatus: podWithHostIPs(), |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| dropDisabledPodStatusFields(tt.podStatus, tt.oldPodStatus, &api.PodSpec{}, &api.PodSpec{}) |
| |
| if !reflect.DeepEqual(tt.podStatus, tt.wantPodStatus) { |
| t.Errorf("dropDisabledStatusFields() = %v, want %v", tt.podStatus, tt.wantPodStatus) |
| } |
| }) |
| } |
| } |
| |
| func makeHostIPs(ips ...string) []api.HostIP { |
| ret := []api.HostIP{} |
| for _, ip := range ips { |
| ret = append(ret, api.HostIP{IP: ip}) |
| } |
| return ret |
| } |
| |
| func TestDropNodeInclusionPolicyFields(t *testing.T) { |
| ignore := api.NodeInclusionPolicyIgnore |
| honor := api.NodeInclusionPolicyHonor |
| |
| tests := []struct { |
| name string |
| enabled bool |
| podSpec *api.PodSpec |
| oldPodSpec *api.PodSpec |
| wantPodSpec *api.PodSpec |
| }{ |
| { |
| name: "feature disabled, both pods don't use the fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature disabled, only old pod use NodeAffinityPolicy field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeAffinityPolicy: &honor}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature disabled, only old pod use NodeTaintsPolicy field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeTaintsPolicy: &ignore}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature disabled, only current pod use NodeAffinityPolicy field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeAffinityPolicy: &honor}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{{ |
| NodeAffinityPolicy: nil, |
| }}, |
| }, |
| }, |
| { |
| name: "feature disabled, only current pod use NodeTaintsPolicy field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeTaintsPolicy: &ignore}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeTaintsPolicy: nil}, |
| }, |
| }, |
| }, |
| { |
| name: "feature disabled, both pods use NodeAffinityPolicy fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeAffinityPolicy: &honor}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeAffinityPolicy: &ignore}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeAffinityPolicy: &ignore}, |
| }, |
| }, |
| }, |
| { |
| name: "feature disabled, both pods use NodeTaintsPolicy fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeTaintsPolicy: &ignore}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeTaintsPolicy: &honor}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {NodeTaintsPolicy: &honor}, |
| }, |
| }, |
| }, |
| { |
| name: "feature enabled, both pods use the fields", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeAffinityPolicy: &ignore, |
| NodeTaintsPolicy: &honor, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeAffinityPolicy: &honor, |
| NodeTaintsPolicy: &ignore, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeAffinityPolicy: &honor, |
| NodeTaintsPolicy: &ignore, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "feature enabled, only old pod use NodeAffinityPolicy field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeAffinityPolicy: &honor, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature enabled, only old pod use NodeTaintsPolicy field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeTaintsPolicy: &ignore, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature enabled, only current pod use NodeAffinityPolicy field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeAffinityPolicy: &ignore, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeAffinityPolicy: &ignore, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "feature enabled, only current pod use NodeTaintsPolicy field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeTaintsPolicy: &honor, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| NodeTaintsPolicy: &honor, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, test.enabled)() |
| |
| dropDisabledFields(test.podSpec, nil, test.oldPodSpec, nil) |
| if diff := cmp.Diff(test.wantPodSpec, test.podSpec); diff != "" { |
| t.Errorf("unexpected pod spec (-want, +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func Test_dropDisabledMatchLabelKeysFieldInPodAffinity(t *testing.T) { |
| tests := []struct { |
| name string |
| enabled bool |
| podSpec *api.PodSpec |
| oldPodSpec *api.PodSpec |
| wantPodSpec *api.PodSpec |
| }{ |
| { |
| name: "[PodAffinity/required] feature disabled, both pods don't use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/required] feature disabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/required] feature disabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{{}}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/required] feature disabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/required] feature enabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/required] feature enabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/required] feature enabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature disabled, both pods don't use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature disabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature disabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{{}}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature disabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature enabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature enabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAffinity/preferred] feature enabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAffinity: &api.PodAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature disabled, both pods don't use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature disabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature disabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{{}}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature disabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature enabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature enabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/required] feature enabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ |
| {MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| |
| { |
| name: "[PodAntiAffinity/preferred] feature disabled, both pods don't use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/preferred] feature disabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/preferred] feature disabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{{}}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/preferred] feature disabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/preferred] feature enabled, only old pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/preferred] feature enabled, only current pod uses MatchLabelKeys/MismatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{}, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "[PodAntiAffinity/preferred] feature enabled, both pods use MatchLabelKeys/MismatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| Affinity: &api.Affinity{ |
| PodAntiAffinity: &api.PodAntiAffinity{ |
| PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ |
| { |
| PodAffinityTerm: api.PodAffinityTerm{MatchLabelKeys: []string{"foo"}, MismatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, test.enabled)() |
| |
| dropDisabledFields(test.podSpec, nil, test.oldPodSpec, nil) |
| if diff := cmp.Diff(test.wantPodSpec, test.podSpec); diff != "" { |
| t.Errorf("unexpected pod spec (-want, +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func Test_dropDisabledMatchLabelKeysFieldInTopologySpread(t *testing.T) { |
| tests := []struct { |
| name string |
| enabled bool |
| podSpec *api.PodSpec |
| oldPodSpec *api.PodSpec |
| wantPodSpec *api.PodSpec |
| }{ |
| { |
| name: "feature disabled, both pods don't use MatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature disabled, only old pod uses MatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature disabled, only current pod uses MatchLabelKeys field", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{{}}, |
| }, |
| }, |
| { |
| name: "feature disabled, both pods use MatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| { |
| name: "feature enabled, only old pod uses MatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| }, |
| { |
| name: "feature enabled, only current pod uses MatchLabelKeys field", |
| enabled: true, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{}, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| { |
| name: "feature enabled, both pods use MatchLabelKeys fields", |
| enabled: false, |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| podSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| wantPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| {MatchLabelKeys: []string{"foo"}}, |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, test.enabled)() |
| |
| dropDisabledFields(test.podSpec, nil, test.oldPodSpec, nil) |
| if diff := cmp.Diff(test.wantPodSpec, test.podSpec); diff != "" { |
| t.Errorf("unexpected pod spec (-want, +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestDropHostUsers(t *testing.T) { |
| falseVar := false |
| trueVar := true |
| |
| podWithoutHostUsers := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| SecurityContext: &api.PodSecurityContext{}}, |
| } |
| } |
| podWithHostUsersFalse := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| SecurityContext: &api.PodSecurityContext{ |
| HostUsers: &falseVar, |
| }, |
| }, |
| } |
| } |
| podWithHostUsersTrue := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| SecurityContext: &api.PodSecurityContext{ |
| HostUsers: &trueVar, |
| }, |
| }, |
| } |
| } |
| |
| podInfo := []struct { |
| description string |
| hasHostUsers bool |
| pod func() *api.Pod |
| }{ |
| { |
| description: "with hostUsers=true", |
| hasHostUsers: true, |
| pod: podWithHostUsersTrue, |
| }, |
| { |
| description: "with hostUsers=false", |
| hasHostUsers: true, |
| pod: podWithHostUsersFalse, |
| }, |
| { |
| description: "with hostUsers=nil", |
| pod: func() *api.Pod { return nil }, |
| }, |
| } |
| |
| for _, enabled := range []bool{true, false} { |
| for _, oldPodInfo := range podInfo { |
| for _, newPodInfo := range podInfo { |
| oldPodHasHostUsers, oldPod := oldPodInfo.hasHostUsers, oldPodInfo.pod() |
| newPodHasHostUsers, newPod := newPodInfo.hasHostUsers, newPodInfo.pod() |
| if newPod == nil { |
| continue |
| } |
| |
| t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.UserNamespacesSupport, enabled)() |
| |
| DropDisabledPodFields(newPod, oldPod) |
| |
| // old pod should never be changed |
| if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) { |
| t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod())) |
| } |
| |
| switch { |
| case enabled || oldPodHasHostUsers: |
| // new pod should not be changed if the feature is enabled, or if the old pod had hostUsers |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| case newPodHasHostUsers: |
| // new pod should be changed |
| if reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod was not changed") |
| } |
| // new pod should not have hostUsers |
| if exp := podWithoutHostUsers(); !reflect.DeepEqual(newPod, exp) { |
| t.Errorf("new pod had hostUsers: %v", cmp.Diff(newPod, exp)) |
| } |
| default: |
| // new pod should not need to be changed |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| } |
| }) |
| } |
| } |
| } |
| |
| } |
| |
| func TestValidateTopologySpreadConstraintLabelSelectorOption(t *testing.T) { |
| testCases := []struct { |
| name string |
| oldPodSpec *api.PodSpec |
| wantOption bool |
| }{ |
| { |
| name: "Create", |
| wantOption: false, |
| }, |
| { |
| name: "UpdateInvalidLabelSelector", |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| LabelSelector: &metav1.LabelSelector{ |
| MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}, |
| }, |
| }, |
| }, |
| }, |
| wantOption: true, |
| }, |
| { |
| name: "UpdateValidLabelSelector", |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| LabelSelector: &metav1.LabelSelector{ |
| MatchLabels: map[string]string{"foo": "foo"}, |
| }, |
| }, |
| }, |
| }, |
| wantOption: false, |
| }, |
| { |
| name: "UpdateEmptyLabelSelector", |
| oldPodSpec: &api.PodSpec{ |
| TopologySpreadConstraints: []api.TopologySpreadConstraint{ |
| { |
| LabelSelector: nil, |
| }, |
| }, |
| }, |
| wantOption: false, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| // Pod meta doesn't impact the outcome. |
| gotOptions := GetValidationOptionsFromPodSpecAndMeta(&api.PodSpec{}, tc.oldPodSpec, nil, nil) |
| if tc.wantOption != gotOptions.AllowInvalidTopologySpreadConstraintLabelSelector { |
| t.Errorf("Got AllowInvalidLabelValueInSelector=%t, want %t", gotOptions.AllowInvalidTopologySpreadConstraintLabelSelector, tc.wantOption) |
| } |
| }) |
| } |
| } |
| |
| func TestValidateAllowNonLocalProjectedTokenPathOption(t *testing.T) { |
| testCases := []struct { |
| name string |
| oldPodSpec *api.PodSpec |
| wantOption bool |
| }{ |
| { |
| name: "Create", |
| wantOption: false, |
| }, |
| { |
| name: "UpdateInvalidProjectedTokenPath", |
| oldPodSpec: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ServiceAccountToken: &api.ServiceAccountTokenProjection{ |
| Path: "../foo", |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantOption: true, |
| }, |
| { |
| name: "UpdateValidProjectedTokenPath", |
| oldPodSpec: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ServiceAccountToken: &api.ServiceAccountTokenProjection{ |
| Path: "foo", |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| wantOption: false, |
| }, |
| { |
| name: "UpdateEmptyProjectedTokenPath", |
| oldPodSpec: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: nil, |
| HostPath: &api.HostPathVolumeSource{Path: "foo"}, |
| }, |
| }, |
| }, |
| }, |
| wantOption: false, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| // Pod meta doesn't impact the outcome. |
| gotOptions := GetValidationOptionsFromPodSpecAndMeta(&api.PodSpec{}, tc.oldPodSpec, nil, nil) |
| if tc.wantOption != gotOptions.AllowNonLocalProjectedTokenPath { |
| t.Errorf("Got AllowNonLocalProjectedTokenPath=%t, want %t", gotOptions.AllowNonLocalProjectedTokenPath, tc.wantOption) |
| } |
| }) |
| } |
| } |
| |
| func TestDropInPlacePodVerticalScaling(t *testing.T) { |
| podWithInPlaceVerticalScaling := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| ResizePolicy: []api.ContainerResizePolicy{ |
| {ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired}, |
| {ResourceName: api.ResourceMemory, RestartPolicy: api.RestartContainer}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| Resize: api.PodResizeStatusInProgress, |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Resources: &api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")}, |
| }, |
| }, |
| }, |
| }, |
| } |
| } |
| podWithoutInPlaceVerticalScaling := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| } |
| } |
| |
| podInfo := []struct { |
| description string |
| hasInPlaceVerticalScaling bool |
| pod func() *api.Pod |
| }{ |
| { |
| description: "has in-place vertical scaling enabled with resources", |
| hasInPlaceVerticalScaling: true, |
| pod: podWithInPlaceVerticalScaling, |
| }, |
| { |
| description: "has in-place vertical scaling disabled", |
| hasInPlaceVerticalScaling: false, |
| pod: podWithoutInPlaceVerticalScaling, |
| }, |
| { |
| description: "is nil", |
| hasInPlaceVerticalScaling: false, |
| pod: func() *api.Pod { return nil }, |
| }, |
| } |
| |
| for _, enabled := range []bool{true, false} { |
| for _, oldPodInfo := range podInfo { |
| for _, newPodInfo := range podInfo { |
| oldPodHasInPlaceVerticalScaling, oldPod := oldPodInfo.hasInPlaceVerticalScaling, oldPodInfo.pod() |
| newPodHasInPlaceVerticalScaling, newPod := newPodInfo.hasInPlaceVerticalScaling, newPodInfo.pod() |
| if newPod == nil { |
| continue |
| } |
| |
| t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, enabled)() |
| |
| var oldPodSpec *api.PodSpec |
| var oldPodStatus *api.PodStatus |
| if oldPod != nil { |
| oldPodSpec = &oldPod.Spec |
| oldPodStatus = &oldPod.Status |
| } |
| dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil) |
| dropDisabledPodStatusFields(&newPod.Status, oldPodStatus, &newPod.Spec, oldPodSpec) |
| |
| // old pod should never be changed |
| if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) { |
| t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod())) |
| } |
| |
| switch { |
| case enabled || oldPodHasInPlaceVerticalScaling: |
| // new pod shouldn't change if feature enabled or if old pod has ResizePolicy set |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| case newPodHasInPlaceVerticalScaling: |
| // new pod should be changed |
| if reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod was not changed") |
| } |
| // new pod should not have ResizePolicy |
| if !reflect.DeepEqual(newPod, podWithoutInPlaceVerticalScaling()) { |
| t.Errorf("new pod has ResizePolicy: %v", cmp.Diff(newPod, podWithoutInPlaceVerticalScaling())) |
| } |
| default: |
| // new pod should not need to be changed |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| } |
| }) |
| } |
| } |
| } |
| } |
| |
| func TestDropSidecarContainers(t *testing.T) { |
| containerRestartPolicyAlways := api.ContainerRestartPolicyAlways |
| |
| podWithSidecarContainers := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| InitContainers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| RestartPolicy: &containerRestartPolicyAlways, |
| }, |
| }, |
| }, |
| } |
| } |
| |
| podWithoutSidecarContainers := func() *api.Pod { |
| return &api.Pod{ |
| Spec: api.PodSpec{ |
| InitContainers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| } |
| } |
| |
| podInfo := []struct { |
| description string |
| hasSidecarContainer bool |
| pod func() *api.Pod |
| }{ |
| { |
| description: "has a sidecar container", |
| hasSidecarContainer: true, |
| pod: podWithSidecarContainers, |
| }, |
| { |
| description: "does not have a sidecar container", |
| hasSidecarContainer: false, |
| pod: podWithoutSidecarContainers, |
| }, |
| { |
| description: "is nil", |
| hasSidecarContainer: false, |
| pod: func() *api.Pod { return nil }, |
| }, |
| } |
| |
| for _, enabled := range []bool{true, false} { |
| for _, oldPodInfo := range podInfo { |
| for _, newPodInfo := range podInfo { |
| oldPodHasSidecarContainer, oldPod := oldPodInfo.hasSidecarContainer, oldPodInfo.pod() |
| newPodHasSidecarContainer, newPod := newPodInfo.hasSidecarContainer, newPodInfo.pod() |
| if newPod == nil { |
| continue |
| } |
| |
| t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SidecarContainers, enabled)() |
| |
| var oldPodSpec *api.PodSpec |
| if oldPod != nil { |
| oldPodSpec = &oldPod.Spec |
| } |
| dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil) |
| |
| // old pod should never be changed |
| if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) { |
| t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod())) |
| } |
| |
| switch { |
| case enabled || oldPodHasSidecarContainer: |
| // new pod shouldn't change if feature enabled or if old pod has |
| // any sidecar container |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| case newPodHasSidecarContainer: |
| // new pod should be changed |
| if reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod was not changed") |
| } |
| // new pod should not have any sidecar container |
| if !reflect.DeepEqual(newPod, podWithoutSidecarContainers()) { |
| t.Errorf("new pod has a sidecar container: %v", cmp.Diff(newPod, podWithoutSidecarContainers())) |
| } |
| default: |
| // new pod should not need to be changed |
| if !reflect.DeepEqual(newPod, newPodInfo.pod()) { |
| t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) |
| } |
| } |
| }) |
| } |
| } |
| } |
| } |
| |
| func TestMarkPodProposedForResize(t *testing.T) { |
| testCases := []struct { |
| desc string |
| newPod *api.Pod |
| oldPod *api.Pod |
| expectedPod *api.Pod |
| }{ |
| { |
| desc: "nil requests", |
| newPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| }, |
| oldPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| }, |
| expectedPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| desc: "resources unchanged", |
| newPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| }, |
| oldPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| }, |
| expectedPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| desc: "resize desired", |
| newPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| { |
| Name: "c2", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| }, |
| { |
| Name: "c2", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| oldPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| { |
| Name: "c2", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| }, |
| { |
| Name: "c2", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| expectedPod: &api.Pod{ |
| Spec: api.PodSpec{ |
| Containers: []api.Container{ |
| { |
| Name: "c1", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| { |
| Name: "c2", |
| Image: "image", |
| Resources: api.ResourceRequirements{ |
| Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")}, |
| Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")}, |
| }, |
| }, |
| }, |
| }, |
| Status: api.PodStatus{ |
| Resize: api.PodResizeStatusProposed, |
| ContainerStatuses: []api.ContainerStatus{ |
| { |
| Name: "c1", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, |
| }, |
| { |
| Name: "c2", |
| Image: "image", |
| AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.desc, func(t *testing.T) { |
| MarkPodProposedForResize(tc.oldPod, tc.newPod) |
| if diff := cmp.Diff(tc.expectedPod, tc.newPod); diff != "" { |
| t.Errorf("unexpected pod spec (-want, +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestDropClusterTrustBundleProjectedVolumes(t *testing.T) { |
| testCases := []struct { |
| description string |
| clusterTrustBundleProjectionEnabled bool |
| oldPod *api.PodSpec |
| newPod *api.PodSpec |
| wantPod *api.PodSpec |
| }{ |
| { |
| description: "feature gate disabled, cannot add CTB volume to pod", |
| oldPod: &api.PodSpec{ |
| Volumes: []api.Volume{}, |
| }, |
| newPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ClusterTrustBundle: &api.ClusterTrustBundleProjection{ |
| Name: pointer.String("foo"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| wantPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| {}, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| }, |
| { |
| description: "feature gate disabled, can keep CTB volume on pod", |
| oldPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ClusterTrustBundle: &api.ClusterTrustBundleProjection{ |
| Name: pointer.String("foo"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| newPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ClusterTrustBundle: &api.ClusterTrustBundleProjection{ |
| Name: pointer.String("foo"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| wantPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ClusterTrustBundle: &api.ClusterTrustBundleProjection{ |
| Name: pointer.String("foo"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| }, |
| { |
| description: "feature gate enabled, can add CTB volume to pod", |
| clusterTrustBundleProjectionEnabled: true, |
| oldPod: &api.PodSpec{ |
| Volumes: []api.Volume{}, |
| }, |
| newPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ClusterTrustBundle: &api.ClusterTrustBundleProjection{ |
| Name: pointer.String("foo"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| wantPod: &api.PodSpec{ |
| Volumes: []api.Volume{ |
| { |
| Name: "foo", |
| VolumeSource: api.VolumeSource{ |
| Projected: &api.ProjectedVolumeSource{ |
| Sources: []api.VolumeProjection{ |
| { |
| ClusterTrustBundle: &api.ClusterTrustBundleProjection{ |
| Name: pointer.String("foo"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.description, func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ClusterTrustBundleProjection, tc.clusterTrustBundleProjectionEnabled)() |
| |
| dropDisabledClusterTrustBundleProjection(tc.newPod, tc.oldPod) |
| if diff := cmp.Diff(tc.newPod, tc.wantPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestDropPodLifecycleSleepAction(t *testing.T) { |
| makeSleepHandler := func() *api.LifecycleHandler { |
| return &api.LifecycleHandler{ |
| Sleep: &api.SleepAction{Seconds: 1}, |
| } |
| } |
| |
| makeExecHandler := func() *api.LifecycleHandler { |
| return &api.LifecycleHandler{ |
| Exec: &api.ExecAction{Command: []string{"foo"}}, |
| } |
| } |
| |
| makeHTTPGetHandler := func() *api.LifecycleHandler { |
| return &api.LifecycleHandler{ |
| HTTPGet: &api.HTTPGetAction{Host: "foo"}, |
| } |
| } |
| |
| makeContainer := func(preStop, postStart *api.LifecycleHandler) api.Container { |
| container := api.Container{Name: "foo"} |
| if preStop != nil || postStart != nil { |
| container.Lifecycle = &api.Lifecycle{ |
| PostStart: postStart, |
| PreStop: preStop, |
| } |
| } |
| return container |
| } |
| |
| makeEphemeralContainer := func(preStop, postStart *api.LifecycleHandler) api.EphemeralContainer { |
| container := api.EphemeralContainer{ |
| EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "foo"}, |
| } |
| if preStop != nil || postStart != nil { |
| container.Lifecycle = &api.Lifecycle{ |
| PostStart: postStart, |
| PreStop: preStop, |
| } |
| } |
| return container |
| } |
| |
| makePod := func(containers []api.Container, initContainers []api.Container, ephemeralContainers []api.EphemeralContainer) *api.PodSpec { |
| return &api.PodSpec{ |
| Containers: containers, |
| InitContainers: initContainers, |
| EphemeralContainers: ephemeralContainers, |
| } |
| } |
| |
| testCases := []struct { |
| gateEnabled bool |
| oldLifecycleHandler *api.LifecycleHandler |
| newLifecycleHandler *api.LifecycleHandler |
| expectLifecycleHandler *api.LifecycleHandler |
| }{ |
| // nil -> nil |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: nil, |
| newLifecycleHandler: nil, |
| expectLifecycleHandler: nil, |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: nil, |
| newLifecycleHandler: nil, |
| expectLifecycleHandler: nil, |
| }, |
| // nil -> exec |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: nil, |
| newLifecycleHandler: makeExecHandler(), |
| expectLifecycleHandler: makeExecHandler(), |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: nil, |
| newLifecycleHandler: makeExecHandler(), |
| expectLifecycleHandler: makeExecHandler(), |
| }, |
| // nil -> sleep |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: nil, |
| newLifecycleHandler: makeSleepHandler(), |
| expectLifecycleHandler: nil, |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: nil, |
| newLifecycleHandler: makeSleepHandler(), |
| expectLifecycleHandler: makeSleepHandler(), |
| }, |
| // exec -> exec |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: makeExecHandler(), |
| newLifecycleHandler: makeExecHandler(), |
| expectLifecycleHandler: makeExecHandler(), |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: makeExecHandler(), |
| newLifecycleHandler: makeExecHandler(), |
| expectLifecycleHandler: makeExecHandler(), |
| }, |
| // exec -> http |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: makeExecHandler(), |
| newLifecycleHandler: makeHTTPGetHandler(), |
| expectLifecycleHandler: makeHTTPGetHandler(), |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: makeExecHandler(), |
| newLifecycleHandler: makeHTTPGetHandler(), |
| expectLifecycleHandler: makeHTTPGetHandler(), |
| }, |
| // exec -> sleep |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: makeExecHandler(), |
| newLifecycleHandler: makeSleepHandler(), |
| expectLifecycleHandler: nil, |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: makeExecHandler(), |
| newLifecycleHandler: makeSleepHandler(), |
| expectLifecycleHandler: makeSleepHandler(), |
| }, |
| // sleep -> exec |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: makeSleepHandler(), |
| newLifecycleHandler: makeExecHandler(), |
| expectLifecycleHandler: makeExecHandler(), |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: makeSleepHandler(), |
| newLifecycleHandler: makeExecHandler(), |
| expectLifecycleHandler: makeExecHandler(), |
| }, |
| // sleep -> sleep |
| { |
| gateEnabled: false, |
| oldLifecycleHandler: makeSleepHandler(), |
| newLifecycleHandler: makeSleepHandler(), |
| expectLifecycleHandler: makeSleepHandler(), |
| }, |
| { |
| gateEnabled: true, |
| oldLifecycleHandler: makeSleepHandler(), |
| newLifecycleHandler: makeSleepHandler(), |
| expectLifecycleHandler: makeSleepHandler(), |
| }, |
| } |
| |
| for i, tc := range testCases { |
| t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLifecycleSleepAction, tc.gateEnabled)() |
| |
| // preStop |
| // container |
| { |
| oldPod := makePod([]api.Container{makeContainer(tc.oldLifecycleHandler.DeepCopy(), nil)}, nil, nil) |
| newPod := makePod([]api.Container{makeContainer(tc.newLifecycleHandler.DeepCopy(), nil)}, nil, nil) |
| expectPod := makePod([]api.Container{makeContainer(tc.expectLifecycleHandler.DeepCopy(), nil)}, nil, nil) |
| dropDisabledFields(newPod, nil, oldPod, nil) |
| if diff := cmp.Diff(expectPod, newPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| } |
| // InitContainer |
| { |
| oldPod := makePod(nil, []api.Container{makeContainer(tc.oldLifecycleHandler.DeepCopy(), nil)}, nil) |
| newPod := makePod(nil, []api.Container{makeContainer(tc.newLifecycleHandler.DeepCopy(), nil)}, nil) |
| expectPod := makePod(nil, []api.Container{makeContainer(tc.expectLifecycleHandler.DeepCopy(), nil)}, nil) |
| dropDisabledFields(newPod, nil, oldPod, nil) |
| if diff := cmp.Diff(expectPod, newPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| } |
| // EphemeralContainer |
| { |
| oldPod := makePod(nil, nil, []api.EphemeralContainer{makeEphemeralContainer(tc.oldLifecycleHandler.DeepCopy(), nil)}) |
| newPod := makePod(nil, nil, []api.EphemeralContainer{makeEphemeralContainer(tc.newLifecycleHandler.DeepCopy(), nil)}) |
| expectPod := makePod(nil, nil, []api.EphemeralContainer{makeEphemeralContainer(tc.expectLifecycleHandler.DeepCopy(), nil)}) |
| dropDisabledFields(newPod, nil, oldPod, nil) |
| if diff := cmp.Diff(expectPod, newPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| } |
| // postStart |
| // container |
| { |
| oldPod := makePod([]api.Container{makeContainer(nil, tc.oldLifecycleHandler.DeepCopy())}, nil, nil) |
| newPod := makePod([]api.Container{makeContainer(nil, tc.newLifecycleHandler.DeepCopy())}, nil, nil) |
| expectPod := makePod([]api.Container{makeContainer(nil, tc.expectLifecycleHandler.DeepCopy())}, nil, nil) |
| dropDisabledFields(newPod, nil, oldPod, nil) |
| if diff := cmp.Diff(expectPod, newPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| } |
| // InitContainer |
| { |
| oldPod := makePod(nil, []api.Container{makeContainer(nil, tc.oldLifecycleHandler.DeepCopy())}, nil) |
| newPod := makePod(nil, []api.Container{makeContainer(nil, tc.newLifecycleHandler.DeepCopy())}, nil) |
| expectPod := makePod(nil, []api.Container{makeContainer(nil, tc.expectLifecycleHandler.DeepCopy())}, nil) |
| dropDisabledFields(newPod, nil, oldPod, nil) |
| if diff := cmp.Diff(expectPod, newPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| } |
| // EphemeralContainer |
| { |
| oldPod := makePod(nil, nil, []api.EphemeralContainer{makeEphemeralContainer(nil, tc.oldLifecycleHandler.DeepCopy())}) |
| newPod := makePod(nil, nil, []api.EphemeralContainer{makeEphemeralContainer(nil, tc.newLifecycleHandler.DeepCopy())}) |
| expectPod := makePod(nil, nil, []api.EphemeralContainer{makeEphemeralContainer(nil, tc.expectLifecycleHandler.DeepCopy())}) |
| dropDisabledFields(newPod, nil, oldPod, nil) |
| if diff := cmp.Diff(expectPod, newPod); diff != "" { |
| t.Fatalf("Unexpected modification to new pod; diff (-got +want)\n%s", diff) |
| } |
| } |
| }) |
| } |
| } |