From a6943ad863ee33466248641158ac87df0cfd80b4 Mon Sep 17 00:00:00 2001 From: areller Date: Tue, 24 May 2022 14:55:24 -0400 Subject: [PATCH] [fix] fix and refactor TestValidateStatefulSet and TestValidateStatefulSet test cases (#109809) * restructure tests restructure TestValidateStatefulSet and TestValidateStatefulSetUpdate to be an array and accept list of expected errors. in this commits, no tests were changed and expected errors are not filled yet * add expected errors adding expected errors and making the tests pass, without making any changes to the tests * clean up TestValidateStatefulSet error cases * clean up TestValidateStatefulSetUpdate cases * validate new statefulset as part of update validation * address PR comments --- pkg/apis/apps/validation/validation.go | 12 +- pkg/apis/apps/validation/validation_test.go | 1400 ++++++++++++------- pkg/registry/apps/statefulset/strategy.go | 4 +- 3 files changed, 889 insertions(+), 527 deletions(-) diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index 24c2f65432f..7429847c41f 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -166,8 +166,11 @@ func ValidateStatefulSet(statefulSet *apps.StatefulSet, opts apivalidation.PodVa } // ValidateStatefulSetUpdate tests if required fields in the StatefulSet are set. -func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata")) +func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, opts apivalidation.PodValidationOptions) field.ErrorList { + // first, validate that the new statefulset is valid + allErrs := ValidateStatefulSet(statefulSet, opts) + + allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))...) // statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this // deep copy right away. This avoids mutating our inputs @@ -187,11 +190,6 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) fi } } - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(statefulSet.Spec.Replicas), field.NewPath("spec", "replicas"))...) - if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) { - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(statefulSet.Spec.MinReadySeconds), field.NewPath("spec", "minReadySeconds"))...) - } - allErrs = append(allErrs, ValidatePersistentVolumeClaimRetentionPolicy(statefulSet.Spec.PersistentVolumeClaimRetentionPolicy, field.NewPath("spec", "persistentVolumeClaimRetentionPolicy"))...) return allErrs } diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index 423b4220f2b..2e31dd6b84a 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -17,17 +17,21 @@ limitations under the License. package validation import ( - "strconv" + "fmt" "strings" "testing" "github.com/davecgh/go-spew/spew" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation/field" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/apps" api "k8s.io/kubernetes/pkg/apis/core" corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" @@ -70,10 +74,10 @@ func TestValidateStatefulSet(t *testing.T) { invalidPodTemplate2 := api.PodTemplate{ Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "bar"}, + Labels: validLabels, }, Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, + RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, ActiveDeadlineSeconds: &invalidTime, }, @@ -82,419 +86,576 @@ func TestValidateStatefulSet(t *testing.T) { const enableStatefulSetAutoDeletePVC = "[enable StatefulSetAutoDeletePVC]" - successCases := map[string]apps.StatefulSet{ - "alpha name": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "alphanumeric name": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "parallel pod management": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.ParallelPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "ordered ready pod management": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType}, - }, - }, - "update strategy": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { - return &apps.RollingUpdateStatefulSetStrategy{Partition: 2} - }()}, - }, - }, - "PVC policy " + enableStatefulSetAutoDeletePVC: { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ - WhenDeleted: apps.DeletePersistentVolumeClaimRetentionPolicyType, - WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, + type testCase struct { + name string + set apps.StatefulSet + errs field.ErrorList + } + + successCases := []testCase{ + { + name: "alpha name", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, }, - "maxUnavailable with parallel pod management": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.ParallelPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ - Partition: 2, - MaxUnavailable: intStrAddr(intstr.FromInt(2)), + { + name: "alphanumeric name", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + }, + { + name: "parallel pod management", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.ParallelPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + }, + { + name: "ordered ready pod management", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType}, + }, + }, + }, + { + name: "update strategy", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{ + Type: apps.RollingUpdateStatefulSetStrategyType, + RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { + return &apps.RollingUpdateStatefulSetStrategy{Partition: 2} + }()}, + }, + }, + }, + { + name: "PVC policy " + enableStatefulSetAutoDeletePVC, + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: apps.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, + }, + }, + }, + }, + { + name: "maxUnavailable with parallel pod management", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.ParallelPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{ + Type: apps.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ + Partition: 2, + MaxUnavailable: intStrAddr(intstr.FromInt(2)), + }, }, }, }, }, } - for name, successCase := range successCases { - name := name - set := successCase - t.Run("success case "+name, func(t *testing.T) { + errorCases := []testCase{ + { + name: "zero-length ID", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Required(field.NewPath("metadata", "name"), ""), + }, + }, + { + name: "missing-namespace", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Required(field.NewPath("metadata", "namespace"), ""), + }, + }, + { + name: "empty selector", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Required(field.NewPath("spec", "selector"), ""), + field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), // selector is empty, labels are not, so select doesn't match labels + }, + }, + { + name: "selector_doesnt_match", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), + }, + }, + { + name: "negative_replicas", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Replicas: -1, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "replicas"), nil, ""), + }, + }, + { + name: "invalid_label", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "labels"), nil, ""), + }, + }, + { + name: "invalid_label 2", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: invalidLabels}, + Template: invalidPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "labels"), nil, ""), + field.Invalid(field.NewPath("spec", "selector"), nil, ""), + field.Invalid(field.NewPath("spec", "selector", "matchLabels"), nil, ""), + }, + }, + { + name: "invalid_annotation", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "annotations"), nil, ""), + }, + }, + { + name: "invalid restart policy 1", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + }, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: validLabels, + }, + }, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), + }, + }, + { + name: "invalid restart policy 2", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + }, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyNever, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: validLabels, + }, + }, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), + }, + }, + { + name: "empty restart policy", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + }, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: validLabels, + }, + }, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), + }, + }, + { + name: "invalid update strategy", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: "foo"}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy"), nil, ""), + }, + }, + { + name: "empty update strategy", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: ""}, + }, + }, + errs: field.ErrorList{ + field.Required(field.NewPath("spec", "updateStrategy"), ""), + }, + }, + { + name: "invalid rolling update", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType, + RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { + return &apps.RollingUpdateStatefulSetStrategy{Partition: 1} + }()}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate"), nil, ""), + }, + }, + { + name: "negative parition", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType, + RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { + return &apps.RollingUpdateStatefulSetStrategy{Partition: -1} + }()}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "partition"), nil, ""), + }, + }, + { + name: "empty pod management policy", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: "", + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Required(field.NewPath("spec", "podManagementPolicy"), ""), + }, + }, + { + name: "invalid pod management policy", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: "foo", + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podManagementPolicy"), nil, ""), + }, + }, + { + name: "set active deadline seconds", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: invalidPodTemplate2.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""), + }, + }, + { + name: "empty PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC, + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{}, + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil), + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil), + }, + }, + { + name: "invalid PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC, + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenScaled: apps.PersistentVolumeClaimRetentionPolicyType("invalid-retention-policy"), + WhenDeleted: apps.PersistentVolumeClaimRetentionPolicyType("invalid-retention-policy"), + }, + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil), + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil), + }, + }, + { + name: "zero maxUnavailable", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{ + Type: apps.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ + MaxUnavailable: intStrAddr(intstr.FromInt(0)), + }, + }, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), + }, + }, + { + name: "zero percent maxUnavailable", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.ParallelPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{ + Type: apps.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ + MaxUnavailable: intStrAddr(intstr.FromString("0%")), + }, + }, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), + }, + }, + { + name: "greater than 100 percent maxUnavailable", + set: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.ParallelPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{ + Type: apps.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ + MaxUnavailable: intStrAddr(intstr.FromString("101%")), + }, + }, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), + }, + }, + } + + cmpOpts := []cmp.Option{cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"), cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() })} + for _, testCase := range append(successCases, errorCases...) { + name := testCase.name + var testTitle string + if len(testCase.errs) == 0 { + testTitle = fmt.Sprintf("success case %s", name) + } else { + testTitle = fmt.Sprintf("error case %s", name) + } + + t.Run(testTitle, func(t *testing.T) { if strings.Contains(name, enableStatefulSetAutoDeletePVC) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)() } - if errs := ValidateStatefulSet(&set, corevalidation.PodValidationOptions{}); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - }) - } - errorCases := map[string]apps.StatefulSet{ - "zero-length ID": { - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "missing-namespace": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "empty selector": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "selector_doesnt_match": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid manifest": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "negative_replicas": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Replicas: -1, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid_label": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid_label 2": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Template: invalidPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid_annotation": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - Annotations: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid restart policy 1": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - }, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: validLabels, - }, - }, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid restart policy 2": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - }, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyNever, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: validLabels, - }, - }, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid update strategy": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: "foo"}, - }, - }, - "empty update strategy": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: ""}, - }, - }, - "invalid rolling update": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType, - RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { - return &apps.RollingUpdateStatefulSetStrategy{Partition: 1} - }()}, - }, - }, - "negative parition": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { - return &apps.RollingUpdateStatefulSetStrategy{Partition: -1} - }()}, - }, - }, - "empty pod management policy": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: "", - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid pod management policy": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: "foo", - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "set active deadline seconds": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: "foo", - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: invalidPodTemplate2.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "empty PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC: { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{}, - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "invalid PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC: { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ - WhenScaled: apps.PersistentVolumeClaimRetentionPolicyType("invalid-delete-policy"), - }, - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - "zero maxUnavailable": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.OrderedReadyPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ - MaxUnavailable: intStrAddr(intstr.FromInt(0)), - }, - }, - }, - }, - "zero percent maxUnavailable": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.ParallelPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ - MaxUnavailable: intStrAddr(intstr.FromString("0%")), - }, - }, - }, - }, - "greater than 100 percent maxUnavailable": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.ParallelPodManagement, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - Replicas: 3, - UpdateStrategy: apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ - MaxUnavailable: intStrAddr(intstr.FromString("101%")), - }, - }, - }, - }, - } - - for k, v := range errorCases { - t.Run(k, func(t *testing.T) { - if strings.Contains(k, enableStatefulSetAutoDeletePVC) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)() - } - errs := ValidateStatefulSet(&v, corevalidation.PodValidationOptions{}) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - - for i := range errs { - field := errs[i].Field - if !strings.HasPrefix(field, "spec.template.") && - field != "metadata.name" && - field != "metadata.namespace" && - field != "spec.selector" && - field != "spec.template" && - field != "GCEPersistentDisk.ReadOnly" && - field != "spec.replicas" && - field != "spec.template.labels" && - field != "metadata.annotations" && - field != "metadata.labels" && - field != "status.replicas" && - field != "spec.updateStrategy" && - field != "spec.updateStrategy.rollingUpdate" && - field != "spec.updateStrategy.rollingUpdate.partition" && - field != "spec.podManagementPolicy" && - field != "spec.persistentVolumeClaimRetentionPolicy" && - field != "spec.persistentVolumeClaimRetentionPolicy.whenDeleted" && - field != "spec.persistentVolumeClaimRetentionPolicy.whenScaled" && - field != "spec.updateStrategy.rollingUpdate.maxUnavailable" && - field != "spec.template.spec.activeDeadlineSeconds" { - t.Errorf("%s: missing prefix for: %v", k, errs[i]) - } + errs := ValidateStatefulSet(&testCase.set, pod.GetValidationOptionsFromPodTemplate(&testCase.set.Spec.Template, nil)) + wantErrs := testCase.errs + if diff := cmp.Diff(wantErrs, errs, cmpOpts...); diff != "" { + t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff) } }) } @@ -750,6 +911,7 @@ func TestValidateStatefulSetStatus(t *testing.T) { func TestValidateStatefulSetUpdate(t *testing.T) { validLabels := map[string]string{"a": "b"} + validLabels2 := map[string]string{"c": "d"} validPodTemplate := api.PodTemplate{ Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -762,6 +924,54 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, } + validPodTemplate2 := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validLabels2, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + } + + storageClass := "storage-class1" + storageClass2 := "storage-class2" + + validPVCTemplate := api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-abc", + }, + Spec: api.PersistentVolumeClaimSpec{ + StorageClassName: &storageClass, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + } + validPVCTemplateChangedSize := *validPVCTemplate.DeepCopy() + validPVCTemplateChangedSize.Spec.Resources.Requests[api.ResourceStorage] = resource.MustParse("2Gi") + + validPVCTemplateChangedClass := *validPVCTemplate.DeepCopy() + validPVCTemplateChangedClass.Spec.StorageClassName = &storageClass2 + + validPVCTemplate2 := api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-abc2", + }, + Spec: api.PersistentVolumeClaimSpec{ + StorageClassName: &storageClass2, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: resource.MustParse("2Gi"), + }, + }, + }, + } addContainersValidTemplate := validPodTemplate.DeepCopy() addContainersValidTemplate.Template.Spec.Containers = append(addContainersValidTemplate.Template.Spec.Containers, @@ -770,38 +980,16 @@ func TestValidateStatefulSetUpdate(t *testing.T) { t.Errorf("failure during test setup: template %v should have more containers than template %v", addContainersValidTemplate, validPodTemplate) } - readWriteVolumePodTemplate := 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"}}, - Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, - }, - }, - } - invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - invalidPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: invalidLabels, - }, - }, - } - - type psUpdateTest struct { + type testCase struct { + name string old apps.StatefulSet update apps.StatefulSet + errs field.ErrorList } - successCases := []psUpdateTest{ + + successCases := []testCase{ { + name: "update replica count", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ @@ -823,6 +1011,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, { + name: "update containers 1", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ @@ -843,6 +1032,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, { + name: "update containers 2", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ @@ -863,6 +1053,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, { + name: "update containers and pvc retention policy 1", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ @@ -887,6 +1078,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, { + name: "update containers and pvc retention policy 2", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ @@ -909,101 +1101,127 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, }, + { + name: "update update strategy", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType}, + }, + }, + }, + { + name: "update min ready seconds 1", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + MinReadySeconds: 10, + }, + }, + }, + { + name: "update min ready seconds 2", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + MinReadySeconds: 5, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + MinReadySeconds: 10, + }, + }, + }, } - for i, successCase := range successCases { - t.Run("success case "+strconv.Itoa(i), func(t *testing.T) { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - if errs := ValidateStatefulSetUpdate(&successCase.update, &successCase.old); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - }) - } - - errorCases := map[string]psUpdateTest{ - "more than one read/write": { + errorCases := []testCase{ + { + name: "update name", old: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - update: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ PodManagementPolicy: apps.OrderedReadyPodManagement, - Replicas: 2, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: readWriteVolumePodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - }, - "empty pod creation policy": { - old: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - update: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - Replicas: 3, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - }, - "invalid pod creation policy": { - old: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, - update: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - PodManagementPolicy: apps.PodManagementPolicyType("Other"), - Replicas: 3, Selector: &metav1.LabelSelector{MatchLabels: validLabels}, Template: validPodTemplate.Template, UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, - }, - "invalid selector": { - old: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: apps.StatefulSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, - }, - }, update: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + ObjectMeta: metav1.ObjectMeta{Name: "abc2", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ PodManagementPolicy: apps.OrderedReadyPodManagement, - Replicas: 2, - Selector: &metav1.LabelSelector{MatchLabels: invalidLabels}, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, Template: validPodTemplate.Template, UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), nil, ""), + }, }, - "invalid pod": { + { + name: "update namespace", old: apps.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault + "1"}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "namespace"), nil, ""), + }, + }, + { + name: "update selector", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ PodManagementPolicy: apps.OrderedReadyPodManagement, Selector: &metav1.LabelSelector{MatchLabels: validLabels}, @@ -1014,19 +1232,72 @@ func TestValidateStatefulSetUpdate(t *testing.T) { update: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ - Replicas: 2, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: invalidPodTemplate.Template, - UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels2}, + Template: validPodTemplate2.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), + }, }, - "negative replicas": { + { + name: "update pod management policy 1", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), + }, + }, + { + name: "update pod management policy 2", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.ParallelPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), + }, + }, + { + name: "update to negative replicas", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, update: apps.StatefulSet{ @@ -1039,13 +1310,108 @@ func TestValidateStatefulSetUpdate(t *testing.T) { UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "replicas"), nil, ""), + }, + }, + { + name: "update pvc template size", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + VolumeClaimTemplates: []api.PersistentVolumeClaim{validPVCTemplate}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + VolumeClaimTemplates: []api.PersistentVolumeClaim{validPVCTemplateChangedSize}, + }, + }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), + }, + }, + { + name: "update pvc template storage class", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + VolumeClaimTemplates: []api.PersistentVolumeClaim{validPVCTemplate}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + VolumeClaimTemplates: []api.PersistentVolumeClaim{validPVCTemplateChangedClass}, + }, + }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), + }, + }, + { + name: "add new pvc template", + old: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + VolumeClaimTemplates: []api.PersistentVolumeClaim{validPVCTemplate}, + }, + }, + update: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + VolumeClaimTemplates: []api.PersistentVolumeClaim{validPVCTemplate, validPVCTemplate2}, + }, + }, + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), + }, }, } - for testName, errorCase := range errorCases { - t.Run(testName, func(t *testing.T) { - if errs := ValidateStatefulSetUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { - t.Errorf("expected failure: %s", testName) + cmpOpts := []cmp.Option{cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"), cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() })} + for _, testCase := range append(successCases, errorCases...) { + name := testCase.name + var testTitle string + if len(testCase.errs) == 0 { + testTitle = fmt.Sprintf("success case %s", name) + } else { + testTitle = fmt.Sprintf("error case %s", name) + } + + t.Run(testTitle, func(t *testing.T) { + testCase.old.ObjectMeta.ResourceVersion = "1" + testCase.update.ObjectMeta.ResourceVersion = "1" + + errs := ValidateStatefulSetUpdate(&testCase.update, &testCase.old, pod.GetValidationOptionsFromPodTemplate(&testCase.update.Spec.Template, &testCase.old.Spec.Template)) + wantErrs := testCase.errs + if diff := cmp.Diff(wantErrs, errs, cmpOpts...); diff != "" { + t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff) } }) } diff --git a/pkg/registry/apps/statefulset/strategy.go b/pkg/registry/apps/statefulset/strategy.go index 00da328154a..66dd8d014e5 100644 --- a/pkg/registry/apps/statefulset/strategy.go +++ b/pkg/registry/apps/statefulset/strategy.go @@ -173,9 +173,7 @@ func (statefulSetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime. oldStatefulSet := old.(*apps.StatefulSet) opts := pod.GetValidationOptionsFromPodTemplate(&newStatefulSet.Spec.Template, &oldStatefulSet.Spec.Template) - validationErrorList := validation.ValidateStatefulSet(newStatefulSet, opts) - updateErrorList := validation.ValidateStatefulSetUpdate(newStatefulSet, oldStatefulSet) - return append(validationErrorList, updateErrorList...) + return validation.ValidateStatefulSetUpdate(newStatefulSet, oldStatefulSet, opts) } // WarningsOnUpdate returns warnings for the given update.