diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index c924b7a6e5d..a2d0cdea975 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -38,6 +38,8 @@ import ( type StatefulSetValidationOptions struct { // Allow invalid DNS1123 ServiceName AllowInvalidServiceName bool + // Skip validating pod template spec, which is used for StatefulSet update + SkipValidatePodTemplateSpec bool } // ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid. @@ -51,7 +53,8 @@ func ValidateStatefulSetName(name string, prefix bool) []string { } // ValidatePodTemplateSpecForStatefulSet validates the given template and ensures that it is in accordance with the desired selector. -func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { +func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path, opts apivalidation.PodValidationOptions, + setOpts StatefulSetValidationOptions) field.ErrorList { allErrs := field.ErrorList{} if template == nil { allErrs = append(allErrs, field.Required(fldPath, "")) @@ -63,10 +66,9 @@ func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, select allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) } } - // TODO: Add validation for PodSpec, currently this will check volumes, which we know will - // fail. We should really check that the union of the given volumes and volumeClaims match - // volume mounts in the containers. - // allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...) + if !setOpts.SkipValidatePodTemplateSpec { + allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath, opts)...) + } allErrs = append(allErrs, unversionedvalidation.ValidateLabels(template.Labels, fldPath.Child("labels"))...) allErrs = append(allErrs, apivalidation.ValidateAnnotations(template.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, apivalidation.ValidatePodSpecificAnnotations(template.Annotations, &template.Spec, fldPath.Child("annotations"), opts)...) @@ -94,6 +96,22 @@ func ValidatePersistentVolumeClaimRetentionPolicy(policy *apps.StatefulSetPersis return allErrs } +func volumesToAddForTemplates(spec *apps.StatefulSetSpec) map[string]api.Volume { + volumes := make(map[string]api.Volume) + templates := spec.VolumeClaimTemplates + for i := range templates { + volumes[templates[i].Name] = api.Volume{ + Name: templates[i].Name, + VolumeSource: api.VolumeSource{ + PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ + ClaimName: templates[i].Name, + }, + }, + } + } + return volumes +} + // ValidateStatefulSetSpec tests if required fields in the StatefulSet spec are set. func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions, setOpts StatefulSetValidationOptions) field.ErrorList { allErrs := field.ErrorList{} @@ -158,7 +176,28 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, op if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "")) } else { - allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(&spec.Template, selector, fldPath.Child("template"), opts)...) + templateToValidate := &spec.Template + if len(spec.Template.Spec.Subdomain) > 0 || len(spec.Template.Spec.Hostname) > 0 || len(spec.VolumeClaimTemplates) > 0 { + templateToValidate = templateToValidate.DeepCopy() + // overwritten by controller to spec.ServiceName in initIdentity, don't validate + templateToValidate.Spec.Subdomain = "" + // overwritten by controller to pod.Name in initIdentity, don't validate + templateToValidate.Spec.Hostname = "" + if len(spec.VolumeClaimTemplates) > 0 { + templateVolumes := volumesToAddForTemplates(spec) + newVolumes := make([]api.Volume, 0, len(templateVolumes)) + for _, v := range templateVolumes { + newVolumes = append(newVolumes, v) + } + for _, v := range templateToValidate.Spec.Volumes { + if _, ok := templateVolumes[v.Name]; !ok { + newVolumes = append(newVolumes, v) + } + } + templateToValidate.Spec.Volumes = newVolumes + } + } + allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(templateToValidate, selector, fldPath.Child("template"), opts, setOpts)...) } if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { @@ -193,6 +232,10 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, op setOpts := StatefulSetValidationOptions{ AllowInvalidServiceName: true, // serviceName is immutable, tolerate existing invalid names on update } + // In order to tolerate the existing sts, we choose to skip the validation error of old sts podTemplateSpec. + if len(ValidateStatefulSetSpec(&oldStatefulSet.Spec, nil, opts, setOpts)) > 0 { + setOpts.SkipValidatePodTemplateSpec = true + } allErrs = append(allErrs, ValidateStatefulSetSpec(&statefulSet.Spec, field.NewPath("spec"), opts, setOpts)...) // statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index 1c49b05394f..14e65c2a2ea 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -141,6 +141,12 @@ func tweakPVCTemplate(pvc ...api.PersistentVolumeClaim) statefulSetTweak { } } +func tweakVolumes(volumes ...api.Volume) statefulSetTweak { + return func(ss *apps.StatefulSet) { + ss.Spec.Template.Spec.Volumes = volumes + } +} + func tweakUpdateStrategyType(t apps.StatefulSetUpdateStrategyType) statefulSetTweak { return func(ss *apps.StatefulSet) { ss.Spec.UpdateStrategy.Type = t @@ -201,6 +207,18 @@ func tweakServiceName(name string) statefulSetTweak { } } +func tweakSubdomain(name string) statefulSetTweak { + return func(ss *apps.StatefulSet) { + ss.Spec.Template.Spec.Subdomain = name + } +} + +func tweakHostname(name string) statefulSetTweak { + return func(ss *apps.StatefulSet) { + ss.Spec.Template.Spec.Hostname = name + } +} + func TestValidateStatefulSet(t *testing.T) { validLabels := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ @@ -209,9 +227,10 @@ func TestValidateStatefulSet(t *testing.T) { Labels: validLabels, }, Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, } @@ -235,7 +254,9 @@ func TestValidateStatefulSet(t *testing.T) { ContainerPort: 12345, Protocol: api.ProtocolTCP, }}, + TerminationMessagePolicy: api.TerminationMessageReadFile, }}, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, } @@ -253,6 +274,33 @@ func TestValidateStatefulSet(t *testing.T) { }, } + podTemplateWithVolumeMounts := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{ + Name: "abc", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Ports: []api.ContainerPort{{ + ContainerPort: 12345, + Protocol: api.ProtocolTCP, + }}, + VolumeMounts: []api.VolumeMount{{ + Name: "test", + MountPath: "/test", + }}, + TerminationMessagePolicy: api.TerminationMessageReadFile, + }}, + TerminationGracePeriodSeconds: ptr.To[int64](30), + }, + }, + } + invalidTime := int64(60) invalidPodTemplate2 := api.PodTemplate{ Template: api.PodTemplateSpec{ @@ -260,9 +308,10 @@ func TestValidateStatefulSet(t *testing.T) { Labels: validLabels, }, Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - ActiveDeadlineSeconds: &invalidTime, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + ActiveDeadlineSeconds: &invalidTime, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, } @@ -317,6 +366,104 @@ func TestValidateStatefulSet(t *testing.T) { tweakReplicas(3), tweakOrdinalsStart(2), ), + }, { + name: "invalid subdomain", + set: mkStatefulSet(&validPodTemplate, + tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"), + ), + }, { + name: "invalid hostname", + set: mkStatefulSet(&validPodTemplate, + tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"), + ), + }, { + name: "both invalid subdomain and hostname", + set: mkStatefulSet(&validPodTemplate, + tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"), + tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"), + ), + }, { + name: "volume template in pod volumeMounts", + set: mkStatefulSet(&podTemplateWithVolumeMounts, + tweakPVCTemplate(api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.VolumeResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }), + ), + }, { + name: "volume templates already present in volumes", + set: mkStatefulSet(&podTemplateWithVolumeMounts, + tweakVolumes([]api.Volume{ + { + Name: "test1", + VolumeSource: api.VolumeSource{ + EmptyDir: &api.EmptyDirVolumeSource{}, + }, + }, + { + Name: "test2", + VolumeSource: api.VolumeSource{ + EmptyDir: &api.EmptyDirVolumeSource{}, + }, + }, + }...), + tweakPVCTemplate([]api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.VolumeResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.VolumeResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "test2"}, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.VolumeResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }}...), + ), + }, { + name: "volume templates present in volumes", + set: mkStatefulSet(&podTemplateWithVolumeMounts, + tweakVolumes([]api.Volume{ + { + Name: "test", + VolumeSource: api.VolumeSource{ + EmptyDir: &api.EmptyDirVolumeSource{}, + }, + }}...), + tweakPVCTemplate(api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.VolumeResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }), + ), }, } @@ -407,6 +554,7 @@ func TestValidateStatefulSet(t *testing.T) { set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy("")), errs: field.ErrorList{ field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), + field.Required(field.NewPath("spec", "template", "spec", "restartPolicy"), ""), }, }, { name: "invalid update strategy", @@ -465,6 +613,7 @@ func TestValidateStatefulSet(t *testing.T) { set: mkStatefulSet(&invalidPodTemplate2, tweakReplicas(3)), errs: field.ErrorList{ field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""), + field.Required(field.NewPath("spec", "template", "spec", "containers"), ""), }, }, { name: "empty PersistentVolumeClaimRetentionPolicy", @@ -534,6 +683,48 @@ func TestValidateStatefulSet(t *testing.T) { errs: field.ErrorList{ field.Invalid(field.NewPath("spec", "serviceName"), "Invalid.Name", ""), }, + }, { + name: "missing volume in pod volumeMounts", + set: mkStatefulSet(&podTemplateWithVolumeMounts), + errs: field.ErrorList{ + field.NotFound(field.NewPath("spec", "template", "spec", "containers[0].volumeMounts[0].name"), "test"), + }, + }, { + name: "want test volumeMount but have test1 volumeTemplate", + set: mkStatefulSet(&podTemplateWithVolumeMounts, + tweakPVCTemplate(api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.VolumeResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + })), + errs: field.ErrorList{ + field.NotFound(field.NewPath("spec", "template", "spec", "containers[0].volumeMounts[0].name"), "test"), + }, + }, { + name: "duplicate volumes", + set: mkStatefulSet(&validPodTemplate, + tweakVolumes([]api.Volume{ + { + Name: "test1", + VolumeSource: api.VolumeSource{ + EmptyDir: &api.EmptyDirVolumeSource{}, + }, + }, + { + Name: "test1", + VolumeSource: api.VolumeSource{ + EmptyDir: &api.EmptyDirVolumeSource{}, + }, + }, + }...)), + errs: field.ErrorList{ + field.Duplicate(field.NewPath("spec", "template", "spec", "volumes[1]", "name"), "test1"), + }, }, } @@ -566,9 +757,10 @@ func generateStatefulSetSpec(minSeconds int32) *apps.StatefulSetSpec { Labels: labels, }, Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, } @@ -758,9 +950,10 @@ func TestValidateStatefulSetUpdate(t *testing.T) { Labels: validLabels, }, Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, } @@ -769,10 +962,42 @@ func TestValidateStatefulSetUpdate(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Labels: validLabels2, }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, + TerminationGracePeriodSeconds: ptr.To[int64](30), + }, + }, + } + validPodTemplateWithVolumeMounts := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validLabels, + }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + Containers: []api.Container{{ + Name: "abc", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: api.TerminationMessageReadFile, + VolumeMounts: []api.VolumeMount{{ + Name: "pvc-abc", + MountPath: "/test", + }}, + }}, + TerminationGracePeriodSeconds: ptr.To[int64](30), + }, + }, + } + invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + invalidPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + Spec: podtest.MakePodSpec(podtest.SetContainers()), + ObjectMeta: metav1.ObjectMeta{ + Labels: invalidLabels, }, }, } @@ -815,7 +1040,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) { addContainersValidTemplate := validPodTemplate.DeepCopy() addContainersValidTemplate.Template.Spec.Containers = append(addContainersValidTemplate.Template.Spec.Containers, - api.Container{Name: "def", Image: "image2", ImagePullPolicy: "IfNotPresent"}) + api.Container{Name: "def", Image: "image2", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}) if len(addContainersValidTemplate.Template.Spec.Containers) != len(validPodTemplate.Template.Spec.Containers)+1 { t.Errorf("failure during test setup: template %v should have more containers than template %v", addContainersValidTemplate, validPodTemplate) } @@ -882,6 +1107,38 @@ func TestValidateStatefulSetUpdate(t *testing.T) { name: "update with invalid .spec.serviceName", old: mkStatefulSet(&validPodTemplate, tweakServiceName("Invalid.Name")), update: mkStatefulSet(&validPodTemplate, tweakServiceName("Invalid.Name"), tweakReplicas(3)), + }, { + name: "invalid old spec (missing volume template) should skip validation", + old: mkStatefulSet(&validPodTemplateWithVolumeMounts), + update: mkStatefulSet(&validPodTemplateWithVolumeMounts, tweakReplicas(3)), + }, { + name: "volume mount ", + old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), + update: mkStatefulSet(&validPodTemplateWithVolumeMounts, tweakPVCTemplate(validPVCTemplate), tweakReplicas(3)), + }, { + name: "valid old spec, invalid subdomain in new spec", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, + tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"), + ), + }, { + name: "valid old spec, invalid hostname in new spec", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, + tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"), + ), + }, { + name: "both invalid subdomain", + old: mkStatefulSet(&validPodTemplate, tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals")), + update: mkStatefulSet(&validPodTemplate, + tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"), + ), + }, { + name: "both invalid hostname", + old: mkStatefulSet(&validPodTemplate, tweakHostname("NoUppercaseOrSpecialCharsLike=Equals")), + update: mkStatefulSet(&validPodTemplate, + tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"), + ), }, } @@ -950,6 +1207,19 @@ func TestValidateStatefulSetUpdate(t *testing.T) { errs: field.ErrorList{ field.Forbidden(field.NewPath("spec"), ""), }, + }, { + name: "valid old spec but invalid new spec", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&invalidPodTemplate), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "template", "labels"), + "NoUppercaseOrSpecialCharsLike=Equals", ""), + field.Invalid(field.NewPath("spec", "template", "labels"), + "map[string]string{\\\"NoUppercaseOrSpecialCharsLike=Equals\\\":\\\"b\\\"}", ""), + field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), + "map[string]string{\\\"NoUppercaseOrSpecialCharsLike=Equals\\\":\\\"b\\\"}", ""), + field.Required(field.NewPath("spec", "template", "spec", "containers"), ""), + }, }, } diff --git a/pkg/registry/apps/statefulset/storage/storage_test.go b/pkg/registry/apps/statefulset/storage/storage_test.go index d19db00fe44..bd26561a678 100644 --- a/pkg/registry/apps/statefulset/storage/storage_test.go +++ b/pkg/registry/apps/statefulset/storage/storage_test.go @@ -38,6 +38,7 @@ import ( "k8s.io/kubernetes/pkg/apis/autoscaling" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/utils/ptr" ) // TODO: allow for global factory override @@ -68,13 +69,15 @@ func validNewStatefulSet() *apps.StatefulSet { Spec: api.PodSpec{ Containers: []api.Container{ { - Name: "test", - Image: "test_image", - ImagePullPolicy: api.PullIfNotPresent, + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePolicy: api.TerminationMessageReadFile, }, }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, Replicas: 7, diff --git a/pkg/registry/apps/statefulset/strategy_test.go b/pkg/registry/apps/statefulset/strategy_test.go index 16b9ccdd237..d9a0c457d34 100644 --- a/pkg/registry/apps/statefulset/strategy_test.go +++ b/pkg/registry/apps/statefulset/strategy_test.go @@ -48,9 +48,10 @@ func TestStatefulSetStrategy(t *testing.T) { Labels: validSelector, }, Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, + TerminationGracePeriodSeconds: ptr.To[int64](30), }, }, } diff --git a/test/integration/etcd/data.go b/test/integration/etcd/data.go index 931554c9f78..60941b2a7a8 100644 --- a/test/integration/etcd/data.go +++ b/test/integration/etcd/data.go @@ -162,7 +162,7 @@ func GetEtcdStorageDataForNamespaceServedAt(namespace string, v string, isEmulat IntroducedVersion: "1.9", }, gvr("apps", "v1", "statefulsets"): { - Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`, + Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"restartPolicy": "Always", "terminationGracePeriodSeconds": 30, "containers": [{"image": "` + image + `", "name": "container6", "terminationMessagePolicy": "File"}]}}}}`, ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3", IntroducedVersion: "1.9", },