...

Source file src/k8s.io/kubernetes/pkg/apis/apps/validation/validation_test.go

Documentation: k8s.io/kubernetes/pkg/apis/apps/validation

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/go-cmp/cmp/cmpopts"
    26  	"k8s.io/apimachinery/pkg/api/resource"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/dump"
    30  	"k8s.io/apimachinery/pkg/util/intstr"
    31  	"k8s.io/apimachinery/pkg/util/validation/field"
    32  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    33  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    34  	"k8s.io/kubernetes/pkg/api/pod"
    35  	"k8s.io/kubernetes/pkg/apis/apps"
    36  	api "k8s.io/kubernetes/pkg/apis/core"
    37  	corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    38  	"k8s.io/kubernetes/pkg/features"
    39  	"k8s.io/utils/ptr"
    40  )
    41  
    42  type statefulSetTweak func(ss *apps.StatefulSet)
    43  
    44  func mkStatefulSet(template *api.PodTemplate, tweaks ...statefulSetTweak) apps.StatefulSet {
    45  	ss := apps.StatefulSet{
    46  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
    47  		Spec: apps.StatefulSetSpec{
    48  			PodManagementPolicy: apps.OrderedReadyPodManagement,
    49  			Selector:            &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
    50  			Template:            template.Template,
    51  			UpdateStrategy:      apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
    52  		},
    53  	}
    54  
    55  	for _, tw := range tweaks {
    56  		tw(&ss)
    57  	}
    58  
    59  	return ss
    60  }
    61  
    62  func tweakName(name string) statefulSetTweak {
    63  	return func(ss *apps.StatefulSet) {
    64  		ss.ObjectMeta.Name = name
    65  	}
    66  }
    67  
    68  func tweakNamespace(ns string) statefulSetTweak {
    69  	return func(ss *apps.StatefulSet) {
    70  		ss.ObjectMeta.Namespace = ns
    71  	}
    72  }
    73  
    74  func tweakLabels(key string, value string) statefulSetTweak {
    75  	return func(ss *apps.StatefulSet) {
    76  		if ss.ObjectMeta.Labels == nil {
    77  			ss.ObjectMeta.Labels = map[string]string{}
    78  		}
    79  		ss.ObjectMeta.Labels[key] = value
    80  	}
    81  }
    82  
    83  func tweakAnnotations(key string, value string) statefulSetTweak {
    84  	return func(ss *apps.StatefulSet) {
    85  		if ss.ObjectMeta.Annotations == nil {
    86  			ss.ObjectMeta.Annotations = map[string]string{}
    87  		}
    88  		ss.ObjectMeta.Annotations[key] = value
    89  	}
    90  }
    91  
    92  func tweakFinalizers(finalizers ...string) statefulSetTweak {
    93  	return func(ss *apps.StatefulSet) {
    94  		ss.ObjectMeta.Finalizers = finalizers
    95  	}
    96  }
    97  
    98  func tweakPodManagementPolicy(policy apps.PodManagementPolicyType) statefulSetTweak {
    99  	return func(ss *apps.StatefulSet) {
   100  		ss.Spec.PodManagementPolicy = policy
   101  	}
   102  }
   103  
   104  func tweakReplicas(replicas int32) statefulSetTweak {
   105  	return func(ss *apps.StatefulSet) {
   106  		ss.Spec.Replicas = replicas
   107  	}
   108  }
   109  
   110  func tweakSelectorLabels(labels map[string]string) statefulSetTweak {
   111  	return func(ss *apps.StatefulSet) {
   112  		if labels == nil {
   113  			ss.Spec.Selector = nil
   114  		} else {
   115  			ss.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels}
   116  		}
   117  	}
   118  }
   119  
   120  func tweakTemplateRestartPolicy(rp api.RestartPolicy) statefulSetTweak {
   121  	return func(ss *apps.StatefulSet) {
   122  		ss.Spec.Template.Spec.RestartPolicy = rp
   123  	}
   124  }
   125  
   126  func tweakMinReadySeconds(t int32) statefulSetTweak {
   127  	return func(ss *apps.StatefulSet) {
   128  		ss.Spec.MinReadySeconds = t
   129  	}
   130  }
   131  
   132  func tweakOrdinalsStart(s int32) statefulSetTweak {
   133  	return func(ss *apps.StatefulSet) {
   134  		ss.Spec.Ordinals = &apps.StatefulSetOrdinals{Start: s}
   135  	}
   136  }
   137  
   138  func tweakPVCTemplate(pvc ...api.PersistentVolumeClaim) statefulSetTweak {
   139  	return func(ss *apps.StatefulSet) {
   140  		ss.Spec.VolumeClaimTemplates = pvc
   141  	}
   142  }
   143  
   144  func tweakUpdateStrategyType(t apps.StatefulSetUpdateStrategyType) statefulSetTweak {
   145  	return func(ss *apps.StatefulSet) {
   146  		ss.Spec.UpdateStrategy.Type = t
   147  	}
   148  }
   149  
   150  func tweakRollingUpdatePartition(partition int32) statefulSetTweak {
   151  	return func(ss *apps.StatefulSet) {
   152  		if ss.Spec.UpdateStrategy.RollingUpdate == nil {
   153  			ss.Spec.UpdateStrategy.RollingUpdate = &apps.RollingUpdateStatefulSetStrategy{}
   154  		}
   155  		ss.Spec.UpdateStrategy.RollingUpdate.Partition = partition
   156  	}
   157  }
   158  
   159  func tweakMaxUnavailable(mu intstr.IntOrString) statefulSetTweak {
   160  	return func(ss *apps.StatefulSet) {
   161  		if ss.Spec.UpdateStrategy.RollingUpdate == nil {
   162  			ss.Spec.UpdateStrategy.RollingUpdate = &apps.RollingUpdateStatefulSetStrategy{}
   163  		}
   164  		ss.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = ptr.To(mu)
   165  	}
   166  }
   167  
   168  func tweakPVCPolicy(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) statefulSetTweak {
   169  	return func(ss *apps.StatefulSet) {
   170  		ss.Spec.PersistentVolumeClaimRetentionPolicy = policy
   171  	}
   172  }
   173  
   174  type pvcPolicyTweak func(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy)
   175  
   176  func mkPVCPolicy(tweaks ...pvcPolicyTweak) *apps.StatefulSetPersistentVolumeClaimRetentionPolicy {
   177  	policy := &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{}
   178  
   179  	for _, tw := range tweaks {
   180  		tw(policy)
   181  	}
   182  
   183  	return policy
   184  }
   185  
   186  func tweakPVCDeletedPolicy(t apps.PersistentVolumeClaimRetentionPolicyType) pvcPolicyTweak {
   187  	return func(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) {
   188  		policy.WhenDeleted = t
   189  	}
   190  }
   191  
   192  func tweakPVCScalePolicy(t apps.PersistentVolumeClaimRetentionPolicyType) pvcPolicyTweak {
   193  	return func(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) {
   194  		policy.WhenScaled = t
   195  	}
   196  }
   197  
   198  func TestValidateStatefulSet(t *testing.T) {
   199  	validLabels := map[string]string{"a": "b"}
   200  	validPodTemplate := api.PodTemplate{
   201  		Template: api.PodTemplateSpec{
   202  			ObjectMeta: metav1.ObjectMeta{
   203  				Labels: validLabels,
   204  			},
   205  			Spec: api.PodSpec{
   206  				RestartPolicy: api.RestartPolicyAlways,
   207  				DNSPolicy:     api.DNSClusterFirst,
   208  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
   209  			},
   210  		},
   211  	}
   212  
   213  	validHostNetPodTemplate := api.PodTemplate{
   214  		Template: api.PodTemplateSpec{
   215  			ObjectMeta: metav1.ObjectMeta{
   216  				Labels: validLabels,
   217  			},
   218  			Spec: api.PodSpec{
   219  				SecurityContext: &api.PodSecurityContext{
   220  					HostNetwork: true,
   221  				},
   222  				RestartPolicy: api.RestartPolicyAlways,
   223  				DNSPolicy:     api.DNSClusterFirst,
   224  				Containers: []api.Container{{
   225  					Name:            "abc",
   226  					Image:           "image",
   227  					ImagePullPolicy: "IfNotPresent",
   228  					Ports: []api.ContainerPort{{
   229  						ContainerPort: 12345,
   230  						Protocol:      api.ProtocolTCP,
   231  					}},
   232  				}},
   233  			},
   234  		},
   235  	}
   236  
   237  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
   238  	invalidPodTemplate := api.PodTemplate{
   239  		Template: api.PodTemplateSpec{
   240  			Spec: api.PodSpec{
   241  				RestartPolicy: api.RestartPolicyAlways,
   242  				DNSPolicy:     api.DNSClusterFirst,
   243  			},
   244  			ObjectMeta: metav1.ObjectMeta{
   245  				Labels: invalidLabels,
   246  			},
   247  		},
   248  	}
   249  
   250  	invalidTime := int64(60)
   251  	invalidPodTemplate2 := api.PodTemplate{
   252  		Template: api.PodTemplateSpec{
   253  			ObjectMeta: metav1.ObjectMeta{
   254  				Labels: validLabels,
   255  			},
   256  			Spec: api.PodSpec{
   257  				RestartPolicy:         api.RestartPolicyAlways,
   258  				DNSPolicy:             api.DNSClusterFirst,
   259  				ActiveDeadlineSeconds: &invalidTime,
   260  			},
   261  		},
   262  	}
   263  
   264  	const enableStatefulSetAutoDeletePVC = "[enable StatefulSetAutoDeletePVC]"
   265  
   266  	type testCase struct {
   267  		name string
   268  		set  apps.StatefulSet
   269  		errs field.ErrorList
   270  	}
   271  
   272  	successCases := []testCase{{
   273  		name: "alpha name",
   274  		set:  mkStatefulSet(&validPodTemplate, tweakName("abc")),
   275  	}, {
   276  		name: "alphanumeric name",
   277  		set:  mkStatefulSet(&validPodTemplate, tweakName("abc-123")),
   278  	}, {
   279  		name: "hostNetwork true",
   280  		set:  mkStatefulSet(&validHostNetPodTemplate),
   281  	}, {
   282  		name: "parallel pod management",
   283  		set:  mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)),
   284  	}, {
   285  		name: "ordered ready pod management",
   286  		set:  mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)),
   287  	}, {
   288  		name: "update strategy",
   289  		set: mkStatefulSet(&validPodTemplate,
   290  			tweakReplicas(3),
   291  			tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
   292  			tweakRollingUpdatePartition(2),
   293  		),
   294  	}, {
   295  		name: "PVC policy " + enableStatefulSetAutoDeletePVC,
   296  		set: mkStatefulSet(&validPodTemplate,
   297  			tweakPVCPolicy(mkPVCPolicy(
   298  				tweakPVCDeletedPolicy(apps.DeletePersistentVolumeClaimRetentionPolicyType),
   299  				tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
   300  			)),
   301  		),
   302  	}, {
   303  		name: "maxUnavailable with parallel pod management",
   304  		set: mkStatefulSet(&validPodTemplate,
   305  			tweakReplicas(3),
   306  			tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
   307  			tweakRollingUpdatePartition(2),
   308  			tweakMaxUnavailable(intstr.FromInt32(2)),
   309  		),
   310  	}, {
   311  		name: "ordinals.start positive value",
   312  		set: mkStatefulSet(&validPodTemplate,
   313  			tweakReplicas(3),
   314  			tweakOrdinalsStart(2),
   315  		),
   316  	},
   317  	}
   318  
   319  	errorCases := []testCase{{
   320  		name: "zero-length name",
   321  		set:  mkStatefulSet(&validPodTemplate, tweakName("")),
   322  		errs: field.ErrorList{
   323  			field.Required(field.NewPath("metadata", "name"), ""),
   324  		},
   325  	}, {
   326  		name: "name-with-dots",
   327  		set:  mkStatefulSet(&validPodTemplate, tweakName("abc.123")),
   328  		errs: field.ErrorList{
   329  			field.Invalid(field.NewPath("metadata", "name"), "abc.123", ""),
   330  		},
   331  	}, {
   332  		name: "long name",
   333  		set:  mkStatefulSet(&validPodTemplate, tweakName(strings.Repeat("a", 64))),
   334  		errs: field.ErrorList{
   335  			field.Invalid(field.NewPath("metadata", "name"), strings.Repeat("a", 64), ""),
   336  		},
   337  	}, {
   338  		name: "missing-namespace",
   339  		set:  mkStatefulSet(&validPodTemplate, tweakNamespace("")),
   340  		errs: field.ErrorList{
   341  			field.Required(field.NewPath("metadata", "namespace"), ""),
   342  		},
   343  	}, {
   344  		name: "empty selector",
   345  		set:  mkStatefulSet(&validPodTemplate, tweakSelectorLabels(nil)),
   346  		errs: field.ErrorList{
   347  			field.Required(field.NewPath("spec", "selector"), ""),
   348  			field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), // selector is empty, labels are not, so select doesn't match labels
   349  		},
   350  	}, {
   351  		name: "selector_doesnt_match",
   352  		set:  mkStatefulSet(&validPodTemplate, tweakSelectorLabels(map[string]string{"foo": "bar"})),
   353  		errs: field.ErrorList{
   354  			field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""),
   355  		},
   356  	}, {
   357  		name: "negative_replicas",
   358  		set:  mkStatefulSet(&validPodTemplate, tweakReplicas(-1)),
   359  		errs: field.ErrorList{
   360  			field.Invalid(field.NewPath("spec", "replicas"), nil, ""),
   361  		},
   362  	}, {
   363  		name: "invalid_label",
   364  		set: mkStatefulSet(&validPodTemplate,
   365  			tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"),
   366  		),
   367  		errs: field.ErrorList{
   368  			field.Invalid(field.NewPath("metadata", "labels"), nil, ""),
   369  		},
   370  	}, {
   371  		name: "invalid_label 2",
   372  		set: mkStatefulSet(&invalidPodTemplate,
   373  			tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"),
   374  			tweakSelectorLabels(invalidLabels),
   375  		),
   376  		errs: field.ErrorList{
   377  			field.Invalid(field.NewPath("metadata", "labels"), nil, ""),
   378  			field.Invalid(field.NewPath("spec", "selector"), nil, ""),
   379  			field.Invalid(field.NewPath("spec", "selector", "matchLabels"), nil, ""),
   380  		},
   381  	}, {
   382  		name: "invalid_annotation",
   383  		set: mkStatefulSet(&validPodTemplate,
   384  			tweakAnnotations("NoUppercaseOrSpecialCharsLike=Equals", "bar"),
   385  		),
   386  		errs: field.ErrorList{
   387  			field.Invalid(field.NewPath("metadata", "annotations"), nil, ""),
   388  		},
   389  	}, {
   390  		name: "invalid restart policy 1",
   391  		set:  mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyOnFailure)),
   392  		errs: field.ErrorList{
   393  			field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
   394  		},
   395  	}, {
   396  		name: "invalid restart policy 2",
   397  		set:  mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyNever)),
   398  		errs: field.ErrorList{
   399  			field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
   400  		},
   401  	}, {
   402  		name: "empty restart policy",
   403  		set:  mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy("")),
   404  		errs: field.ErrorList{
   405  			field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
   406  		},
   407  	}, {
   408  		name: "invalid update strategy",
   409  		set: mkStatefulSet(&validPodTemplate,
   410  			tweakReplicas(3),
   411  			tweakUpdateStrategyType("foo"),
   412  		),
   413  		errs: field.ErrorList{
   414  			field.Invalid(field.NewPath("spec", "updateStrategy"), nil, ""),
   415  		},
   416  	}, {
   417  		name: "empty update strategy",
   418  		set: mkStatefulSet(&validPodTemplate,
   419  			tweakReplicas(3),
   420  			tweakUpdateStrategyType(""),
   421  		),
   422  		errs: field.ErrorList{
   423  			field.Required(field.NewPath("spec", "updateStrategy"), ""),
   424  		},
   425  	}, {
   426  		name: "invalid rolling update",
   427  		set: mkStatefulSet(&validPodTemplate,
   428  			tweakReplicas(3),
   429  			tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType),
   430  			tweakRollingUpdatePartition(1),
   431  		),
   432  		errs: field.ErrorList{
   433  			field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate"), nil, ""),
   434  		},
   435  	}, {
   436  		name: "negative parition",
   437  		set: mkStatefulSet(&validPodTemplate,
   438  			tweakReplicas(3),
   439  			tweakRollingUpdatePartition(-1),
   440  		),
   441  		errs: field.ErrorList{
   442  			field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "partition"), nil, ""),
   443  		},
   444  	}, {
   445  		name: "empty pod management policy",
   446  		set: mkStatefulSet(&validPodTemplate,
   447  			tweakPodManagementPolicy(""),
   448  			tweakReplicas(3),
   449  		),
   450  		errs: field.ErrorList{
   451  			field.Required(field.NewPath("spec", "podManagementPolicy"), ""),
   452  		},
   453  	}, {
   454  		name: "invalid pod management policy",
   455  		set:  mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("foo")),
   456  		errs: field.ErrorList{
   457  			field.Invalid(field.NewPath("spec", "podManagementPolicy"), nil, ""),
   458  		},
   459  	}, {
   460  		name: "set active deadline seconds",
   461  		set:  mkStatefulSet(&invalidPodTemplate2, tweakReplicas(3)),
   462  		errs: field.ErrorList{
   463  			field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""),
   464  		},
   465  	}, {
   466  		name: "empty PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC,
   467  		set: mkStatefulSet(&validPodTemplate,
   468  			tweakPVCPolicy(mkPVCPolicy()),
   469  		),
   470  		errs: field.ErrorList{
   471  			field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil),
   472  			field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil),
   473  		},
   474  	}, {
   475  		name: "invalid PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC,
   476  		set: mkStatefulSet(&validPodTemplate,
   477  			tweakPVCPolicy(mkPVCPolicy(
   478  				tweakPVCDeletedPolicy("invalid-retention-policy"),
   479  				tweakPVCScalePolicy("invalid-retention-policy"),
   480  			)),
   481  		),
   482  		errs: field.ErrorList{
   483  			field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil),
   484  			field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil),
   485  		},
   486  	}, {
   487  		name: "zero maxUnavailable",
   488  		set: mkStatefulSet(&validPodTemplate,
   489  			tweakReplicas(3),
   490  			tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
   491  			tweakMaxUnavailable(intstr.FromInt32(0)),
   492  		),
   493  		errs: field.ErrorList{
   494  			field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
   495  		},
   496  	}, {
   497  		name: "zero percent maxUnavailable",
   498  		set: mkStatefulSet(&validPodTemplate,
   499  			tweakReplicas(3),
   500  			tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
   501  			tweakMaxUnavailable(intstr.FromString("0%")),
   502  		),
   503  		errs: field.ErrorList{
   504  			field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
   505  		},
   506  	}, {
   507  		name: "greater than 100 percent maxUnavailable",
   508  		set: mkStatefulSet(&validPodTemplate,
   509  			tweakReplicas(3),
   510  			tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
   511  			tweakMaxUnavailable(intstr.FromString("101%")),
   512  		),
   513  		errs: field.ErrorList{
   514  			field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
   515  		},
   516  	}, {
   517  		name: "invalid ordinals.start",
   518  		set: mkStatefulSet(&validPodTemplate,
   519  			tweakReplicas(3),
   520  			tweakOrdinalsStart(-2),
   521  		),
   522  		errs: field.ErrorList{
   523  			field.Invalid(field.NewPath("spec", "ordinals.start"), nil, ""),
   524  		},
   525  	},
   526  	}
   527  
   528  	cmpOpts := []cmp.Option{cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"), cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() })}
   529  	for _, testCase := range append(successCases, errorCases...) {
   530  		name := testCase.name
   531  		var testTitle string
   532  		if len(testCase.errs) == 0 {
   533  			testTitle = fmt.Sprintf("success case %s", name)
   534  		} else {
   535  			testTitle = fmt.Sprintf("error case %s", name)
   536  		}
   537  
   538  		t.Run(testTitle, func(t *testing.T) {
   539  			if strings.Contains(name, enableStatefulSetAutoDeletePVC) {
   540  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)()
   541  			}
   542  
   543  			errs := ValidateStatefulSet(&testCase.set, pod.GetValidationOptionsFromPodTemplate(&testCase.set.Spec.Template, nil))
   544  			wantErrs := testCase.errs
   545  			if diff := cmp.Diff(wantErrs, errs, cmpOpts...); diff != "" {
   546  				t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff)
   547  			}
   548  		})
   549  	}
   550  }
   551  
   552  // generateStatefulSetSpec generates a valid StatefulSet spec
   553  func generateStatefulSetSpec(minSeconds int32) *apps.StatefulSetSpec {
   554  	labels := map[string]string{"a": "b"}
   555  	podTemplate := api.PodTemplate{
   556  		Template: api.PodTemplateSpec{
   557  			ObjectMeta: metav1.ObjectMeta{
   558  				Labels: labels,
   559  			},
   560  			Spec: api.PodSpec{
   561  				RestartPolicy: api.RestartPolicyAlways,
   562  				DNSPolicy:     api.DNSClusterFirst,
   563  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
   564  			},
   565  		},
   566  	}
   567  	ss := &apps.StatefulSetSpec{
   568  		PodManagementPolicy: "OrderedReady",
   569  		Selector:            &metav1.LabelSelector{MatchLabels: labels},
   570  		Template:            podTemplate.Template,
   571  		Replicas:            3,
   572  		UpdateStrategy:      apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
   573  		MinReadySeconds:     minSeconds,
   574  	}
   575  	return ss
   576  }
   577  
   578  // TestValidateStatefulSetMinReadySeconds tests the StatefulSet Spec's minReadySeconds field
   579  func TestValidateStatefulSetMinReadySeconds(t *testing.T) {
   580  	testCases := map[string]struct {
   581  		ss        *apps.StatefulSetSpec
   582  		expectErr bool
   583  	}{
   584  		"valid : minReadySeconds enabled, zero": {
   585  			ss:        generateStatefulSetSpec(0),
   586  			expectErr: false,
   587  		},
   588  		"invalid : minReadySeconds enabled, negative": {
   589  			ss:        generateStatefulSetSpec(-1),
   590  			expectErr: true,
   591  		},
   592  		"valid : minReadySeconds enabled, very large value": {
   593  			ss:        generateStatefulSetSpec(2147483647),
   594  			expectErr: false,
   595  		},
   596  		"invalid : minReadySeconds enabled, large negative": {
   597  			ss:        generateStatefulSetSpec(-2147483648),
   598  			expectErr: true,
   599  		},
   600  	}
   601  	for tcName, tc := range testCases {
   602  		t.Run(tcName, func(t *testing.T) {
   603  			errs := ValidateStatefulSetSpec(tc.ss, field.NewPath("spec", "minReadySeconds"),
   604  				corevalidation.PodValidationOptions{})
   605  			if tc.expectErr && len(errs) == 0 {
   606  				t.Errorf("Unexpected success")
   607  			}
   608  			if !tc.expectErr && len(errs) != 0 {
   609  				t.Errorf("Unexpected error(s): %v", errs)
   610  			}
   611  		})
   612  	}
   613  }
   614  
   615  func TestValidateStatefulSetStatus(t *testing.T) {
   616  	observedGenerationMinusOne := int64(-1)
   617  	collisionCountMinusOne := int32(-1)
   618  	tests := []struct {
   619  		name               string
   620  		replicas           int32
   621  		readyReplicas      int32
   622  		currentReplicas    int32
   623  		updatedReplicas    int32
   624  		availableReplicas  int32
   625  		observedGeneration *int64
   626  		collisionCount     *int32
   627  		expectedErr        bool
   628  	}{{
   629  		name:            "valid status",
   630  		replicas:        3,
   631  		readyReplicas:   3,
   632  		currentReplicas: 2,
   633  		updatedReplicas: 1,
   634  		expectedErr:     false,
   635  	}, {
   636  		name:            "invalid replicas",
   637  		replicas:        -1,
   638  		readyReplicas:   3,
   639  		currentReplicas: 2,
   640  		updatedReplicas: 1,
   641  		expectedErr:     true,
   642  	}, {
   643  		name:            "invalid readyReplicas",
   644  		replicas:        3,
   645  		readyReplicas:   -1,
   646  		currentReplicas: 2,
   647  		updatedReplicas: 1,
   648  		expectedErr:     true,
   649  	}, {
   650  		name:            "invalid currentReplicas",
   651  		replicas:        3,
   652  		readyReplicas:   3,
   653  		currentReplicas: -1,
   654  		updatedReplicas: 1,
   655  		expectedErr:     true,
   656  	}, {
   657  		name:            "invalid updatedReplicas",
   658  		replicas:        3,
   659  		readyReplicas:   3,
   660  		currentReplicas: 2,
   661  		updatedReplicas: -1,
   662  		expectedErr:     true,
   663  	}, {
   664  		name:               "invalid observedGeneration",
   665  		replicas:           3,
   666  		readyReplicas:      3,
   667  		currentReplicas:    2,
   668  		updatedReplicas:    1,
   669  		observedGeneration: &observedGenerationMinusOne,
   670  		expectedErr:        true,
   671  	}, {
   672  		name:            "invalid collisionCount",
   673  		replicas:        3,
   674  		readyReplicas:   3,
   675  		currentReplicas: 2,
   676  		updatedReplicas: 1,
   677  		collisionCount:  &collisionCountMinusOne,
   678  		expectedErr:     true,
   679  	}, {
   680  		name:            "readyReplicas greater than replicas",
   681  		replicas:        3,
   682  		readyReplicas:   4,
   683  		currentReplicas: 2,
   684  		updatedReplicas: 1,
   685  		expectedErr:     true,
   686  	}, {
   687  		name:            "currentReplicas greater than replicas",
   688  		replicas:        3,
   689  		readyReplicas:   3,
   690  		currentReplicas: 4,
   691  		updatedReplicas: 1,
   692  		expectedErr:     true,
   693  	}, {
   694  		name:            "updatedReplicas greater than replicas",
   695  		replicas:        3,
   696  		readyReplicas:   3,
   697  		currentReplicas: 2,
   698  		updatedReplicas: 4,
   699  		expectedErr:     true,
   700  	}, {
   701  		name:              "invalid: number of available replicas",
   702  		replicas:          3,
   703  		readyReplicas:     3,
   704  		currentReplicas:   2,
   705  		availableReplicas: int32(-1),
   706  		expectedErr:       true,
   707  	}, {
   708  		name:              "invalid: available replicas greater than replicas",
   709  		replicas:          3,
   710  		readyReplicas:     3,
   711  		currentReplicas:   2,
   712  		availableReplicas: int32(4),
   713  		expectedErr:       true,
   714  	}, {
   715  		name:              "invalid: available replicas greater than ready replicas",
   716  		replicas:          3,
   717  		readyReplicas:     2,
   718  		currentReplicas:   2,
   719  		availableReplicas: int32(3),
   720  		expectedErr:       true,
   721  	},
   722  	}
   723  
   724  	for _, test := range tests {
   725  		t.Run(test.name, func(t *testing.T) {
   726  			status := apps.StatefulSetStatus{
   727  				Replicas:           test.replicas,
   728  				ReadyReplicas:      test.readyReplicas,
   729  				CurrentReplicas:    test.currentReplicas,
   730  				UpdatedReplicas:    test.updatedReplicas,
   731  				ObservedGeneration: test.observedGeneration,
   732  				CollisionCount:     test.collisionCount,
   733  				AvailableReplicas:  test.availableReplicas,
   734  			}
   735  
   736  			errs := ValidateStatefulSetStatus(&status, field.NewPath("status"))
   737  			if hasErr := len(errs) > 0; hasErr != test.expectedErr {
   738  				t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errs.ToAggregate().Error())
   739  			}
   740  		})
   741  	}
   742  }
   743  
   744  func TestValidateStatefulSetUpdate(t *testing.T) {
   745  	validLabels := map[string]string{"a": "b"}
   746  	validLabels2 := map[string]string{"c": "d"}
   747  	validPodTemplate := api.PodTemplate{
   748  		Template: api.PodTemplateSpec{
   749  			ObjectMeta: metav1.ObjectMeta{
   750  				Labels: validLabels,
   751  			},
   752  			Spec: api.PodSpec{
   753  				RestartPolicy: api.RestartPolicyAlways,
   754  				DNSPolicy:     api.DNSClusterFirst,
   755  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
   756  			},
   757  		},
   758  	}
   759  	validPodTemplate2 := api.PodTemplate{
   760  		Template: api.PodTemplateSpec{
   761  			ObjectMeta: metav1.ObjectMeta{
   762  				Labels: validLabels2,
   763  			},
   764  			Spec: api.PodSpec{
   765  				RestartPolicy: api.RestartPolicyAlways,
   766  				DNSPolicy:     api.DNSClusterFirst,
   767  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
   768  			},
   769  		},
   770  	}
   771  
   772  	storageClass := "storage-class1"
   773  	storageClass2 := "storage-class2"
   774  
   775  	validPVCTemplate := api.PersistentVolumeClaim{
   776  		ObjectMeta: metav1.ObjectMeta{
   777  			Name: "pvc-abc",
   778  		},
   779  		Spec: api.PersistentVolumeClaimSpec{
   780  			StorageClassName: &storageClass,
   781  			Resources: api.VolumeResourceRequirements{
   782  				Requests: api.ResourceList{
   783  					api.ResourceStorage: resource.MustParse("1Gi"),
   784  				},
   785  			},
   786  		},
   787  	}
   788  	validPVCTemplateChangedSize := *validPVCTemplate.DeepCopy()
   789  	validPVCTemplateChangedSize.Spec.Resources.Requests[api.ResourceStorage] = resource.MustParse("2Gi")
   790  
   791  	validPVCTemplateChangedClass := *validPVCTemplate.DeepCopy()
   792  	validPVCTemplateChangedClass.Spec.StorageClassName = &storageClass2
   793  
   794  	validPVCTemplate2 := api.PersistentVolumeClaim{
   795  		ObjectMeta: metav1.ObjectMeta{
   796  			Name: "pvc-abc2",
   797  		},
   798  		Spec: api.PersistentVolumeClaimSpec{
   799  			StorageClassName: &storageClass2,
   800  			Resources: api.VolumeResourceRequirements{
   801  				Requests: api.ResourceList{
   802  					api.ResourceStorage: resource.MustParse("2Gi"),
   803  				},
   804  			},
   805  		},
   806  	}
   807  
   808  	addContainersValidTemplate := validPodTemplate.DeepCopy()
   809  	addContainersValidTemplate.Template.Spec.Containers = append(addContainersValidTemplate.Template.Spec.Containers,
   810  		api.Container{Name: "def", Image: "image2", ImagePullPolicy: "IfNotPresent"})
   811  	if len(addContainersValidTemplate.Template.Spec.Containers) != len(validPodTemplate.Template.Spec.Containers)+1 {
   812  		t.Errorf("failure during test setup: template %v should have more containers than template %v", addContainersValidTemplate, validPodTemplate)
   813  	}
   814  
   815  	type testCase struct {
   816  		name   string
   817  		old    apps.StatefulSet
   818  		update apps.StatefulSet
   819  		errs   field.ErrorList
   820  	}
   821  
   822  	successCases := []testCase{{
   823  		name:   "update replica count",
   824  		old:    mkStatefulSet(&validPodTemplate),
   825  		update: mkStatefulSet(&validPodTemplate, tweakReplicas(3)),
   826  	}, {
   827  		name:   "update containers 1",
   828  		old:    mkStatefulSet(&validPodTemplate),
   829  		update: mkStatefulSet(addContainersValidTemplate),
   830  	}, {
   831  		name:   "update containers 2",
   832  		old:    mkStatefulSet(addContainersValidTemplate),
   833  		update: mkStatefulSet(&validPodTemplate),
   834  	}, {
   835  		name: "update containers and pvc retention policy 1",
   836  		old:  mkStatefulSet(addContainersValidTemplate),
   837  		update: mkStatefulSet(&validPodTemplate,
   838  			tweakPVCPolicy(mkPVCPolicy(
   839  				tweakPVCDeletedPolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
   840  				tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
   841  			)),
   842  		),
   843  	}, {
   844  		name: "update containers and pvc retention policy 2",
   845  		old: mkStatefulSet(&validPodTemplate,
   846  			tweakPVCPolicy(mkPVCPolicy(
   847  				tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
   848  			)),
   849  		),
   850  		update: mkStatefulSet(&validPodTemplate),
   851  	}, {
   852  		name: "update update strategy",
   853  		old:  mkStatefulSet(&validPodTemplate),
   854  		update: mkStatefulSet(&validPodTemplate,
   855  			tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType),
   856  		),
   857  	}, {
   858  		name:   "update min ready seconds 1",
   859  		old:    mkStatefulSet(&validPodTemplate),
   860  		update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)),
   861  	}, {
   862  		name:   "update min ready seconds 2",
   863  		old:    mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(5)),
   864  		update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)),
   865  	}, {
   866  		name:   "update existing instance with now-invalid name",
   867  		old:    mkStatefulSet(&validPodTemplate, tweakFinalizers("final")),
   868  		update: mkStatefulSet(&validPodTemplate, tweakFinalizers()),
   869  	}, {
   870  		name:   "update existing instance with .spec.ordinals.start",
   871  		old:    mkStatefulSet(&validPodTemplate),
   872  		update: mkStatefulSet(&validPodTemplate, tweakOrdinalsStart(3)),
   873  	},
   874  	}
   875  
   876  	errorCases := []testCase{{
   877  		name:   "update name",
   878  		old:    mkStatefulSet(&validPodTemplate, tweakName("abc")),
   879  		update: mkStatefulSet(&validPodTemplate, tweakName("abc2")),
   880  		errs: field.ErrorList{
   881  			field.Invalid(field.NewPath("metadata", "name"), nil, ""),
   882  		},
   883  	}, {
   884  		name:   "update namespace",
   885  		old:    mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault)),
   886  		update: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault+"1")),
   887  		errs: field.ErrorList{
   888  			field.Invalid(field.NewPath("metadata", "namespace"), nil, ""),
   889  		},
   890  	}, {
   891  		name: "update selector",
   892  		old:  mkStatefulSet(&validPodTemplate, tweakSelectorLabels(validLabels)),
   893  		update: mkStatefulSet(&validPodTemplate2,
   894  			tweakSelectorLabels(validLabels2),
   895  		),
   896  		errs: field.ErrorList{
   897  			field.Forbidden(field.NewPath("spec"), ""),
   898  		},
   899  	}, {
   900  		name:   "update pod management policy 1",
   901  		old:    mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("")),
   902  		update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)),
   903  		errs: field.ErrorList{
   904  			field.Forbidden(field.NewPath("spec"), ""),
   905  		},
   906  	}, {
   907  		name:   "update pod management policy 2",
   908  		old:    mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)),
   909  		update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)),
   910  		errs: field.ErrorList{
   911  			field.Forbidden(field.NewPath("spec"), ""),
   912  		},
   913  	}, {
   914  		name:   "update to negative replicas",
   915  		old:    mkStatefulSet(&validPodTemplate),
   916  		update: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)),
   917  		errs: field.ErrorList{
   918  			field.Invalid(field.NewPath("spec", "replicas"), nil, ""),
   919  		},
   920  	}, {
   921  		name:   "update pvc template size",
   922  		old:    mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
   923  		update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedSize)),
   924  		errs: field.ErrorList{
   925  			field.Forbidden(field.NewPath("spec"), ""),
   926  		},
   927  	}, {
   928  		name:   "update pvc template storage class",
   929  		old:    mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
   930  		update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedClass)),
   931  		errs: field.ErrorList{
   932  			field.Forbidden(field.NewPath("spec"), ""),
   933  		},
   934  	}, {
   935  		name:   "add new pvc template",
   936  		old:    mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
   937  		update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate, validPVCTemplate2)),
   938  		errs: field.ErrorList{
   939  			field.Forbidden(field.NewPath("spec"), ""),
   940  		},
   941  	},
   942  	}
   943  
   944  	cmpOpts := []cmp.Option{
   945  		cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"),
   946  		cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() }),
   947  		cmpopts.EquateEmpty(),
   948  	}
   949  	for _, testCase := range append(successCases, errorCases...) {
   950  		name := testCase.name
   951  		var testTitle string
   952  		if len(testCase.errs) == 0 {
   953  			testTitle = fmt.Sprintf("success case %s", name)
   954  		} else {
   955  			testTitle = fmt.Sprintf("error case %s", name)
   956  		}
   957  
   958  		t.Run(testTitle, func(t *testing.T) {
   959  			testCase.old.ObjectMeta.ResourceVersion = "1"
   960  			testCase.update.ObjectMeta.ResourceVersion = "1"
   961  
   962  			errs := ValidateStatefulSetUpdate(&testCase.update, &testCase.old, pod.GetValidationOptionsFromPodTemplate(&testCase.update.Spec.Template, &testCase.old.Spec.Template))
   963  			wantErrs := testCase.errs
   964  			if diff := cmp.Diff(wantErrs, errs, cmpOpts...); diff != "" {
   965  				t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff)
   966  			}
   967  		})
   968  	}
   969  }
   970  
   971  func TestValidateControllerRevision(t *testing.T) {
   972  	newControllerRevision := func(name, namespace string, data runtime.Object, revision int64) apps.ControllerRevision {
   973  		return apps.ControllerRevision{
   974  			ObjectMeta: metav1.ObjectMeta{
   975  				Name:      name,
   976  				Namespace: namespace,
   977  			},
   978  			Data:     data,
   979  			Revision: revision,
   980  		}
   981  	}
   982  
   983  	podTemplate := api.PodTemplate{
   984  		Template: api.PodTemplateSpec{
   985  			Spec: api.PodSpec{
   986  				RestartPolicy: api.RestartPolicyAlways,
   987  				DNSPolicy:     api.DNSClusterFirst,
   988  			},
   989  			ObjectMeta: metav1.ObjectMeta{
   990  				Labels: map[string]string{"a": "b"},
   991  			},
   992  		},
   993  	}
   994  
   995  	ss := mkStatefulSet(&podTemplate)
   996  
   997  	var (
   998  		valid       = newControllerRevision("validname", "validns", &ss, 0)
   999  		badRevision = newControllerRevision("validname", "validns", &ss, -1)
  1000  		emptyName   = newControllerRevision("", "validns", &ss, 0)
  1001  		invalidName = newControllerRevision("NoUppercaseOrSpecialCharsLike=Equals", "validns", &ss, 0)
  1002  		emptyNs     = newControllerRevision("validname", "", &ss, 100)
  1003  		invalidNs   = newControllerRevision("validname", "NoUppercaseOrSpecialCharsLike=Equals", &ss, 100)
  1004  		nilData     = newControllerRevision("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, 100)
  1005  	)
  1006  
  1007  	tests := map[string]struct {
  1008  		history apps.ControllerRevision
  1009  		isValid bool
  1010  	}{
  1011  		"valid":             {valid, true},
  1012  		"negative revision": {badRevision, false},
  1013  		"empty name":        {emptyName, false},
  1014  		"invalid name":      {invalidName, false},
  1015  		"empty namespace":   {emptyNs, false},
  1016  		"invalid namespace": {invalidNs, false},
  1017  		"nil data":          {nilData, false},
  1018  	}
  1019  
  1020  	for name, tc := range tests {
  1021  		t.Run(name, func(t *testing.T) {
  1022  			errs := ValidateControllerRevision(&tc.history)
  1023  			if tc.isValid && len(errs) > 0 {
  1024  				t.Errorf("%v: unexpected error: %v", name, errs)
  1025  			}
  1026  			if !tc.isValid && len(errs) == 0 {
  1027  				t.Errorf("%v: unexpected non-error", name)
  1028  			}
  1029  		})
  1030  	}
  1031  }
  1032  
  1033  func TestValidateControllerRevisionUpdate(t *testing.T) {
  1034  	newControllerRevision := func(version, name, namespace string, data runtime.Object, revision int64) apps.ControllerRevision {
  1035  		return apps.ControllerRevision{
  1036  			ObjectMeta: metav1.ObjectMeta{
  1037  				Name:            name,
  1038  				Namespace:       namespace,
  1039  				ResourceVersion: version,
  1040  			},
  1041  			Data:     data,
  1042  			Revision: revision,
  1043  		}
  1044  	}
  1045  
  1046  	podTemplate := api.PodTemplate{
  1047  		Template: api.PodTemplateSpec{
  1048  			Spec: api.PodSpec{
  1049  				RestartPolicy: api.RestartPolicyAlways,
  1050  				DNSPolicy:     api.DNSClusterFirst,
  1051  			},
  1052  			ObjectMeta: metav1.ObjectMeta{
  1053  				Labels: map[string]string{"a": "b"},
  1054  			},
  1055  		},
  1056  	}
  1057  
  1058  	ss := mkStatefulSet(&podTemplate, tweakName("abc"))
  1059  	modifiedss := mkStatefulSet(&podTemplate, tweakName("cdf"))
  1060  
  1061  	var (
  1062  		valid           = newControllerRevision("1", "validname", "validns", &ss, 0)
  1063  		noVersion       = newControllerRevision("", "validname", "validns", &ss, 0)
  1064  		changedData     = newControllerRevision("1", "validname", "validns", &modifiedss, 0)
  1065  		changedRevision = newControllerRevision("1", "validname", "validns", &ss, 1)
  1066  	)
  1067  
  1068  	cases := []struct {
  1069  		name       string
  1070  		newHistory apps.ControllerRevision
  1071  		oldHistory apps.ControllerRevision
  1072  		isValid    bool
  1073  	}{{
  1074  		name:       "valid",
  1075  		newHistory: valid,
  1076  		oldHistory: valid,
  1077  		isValid:    true,
  1078  	}, {
  1079  		name:       "invalid",
  1080  		newHistory: noVersion,
  1081  		oldHistory: valid,
  1082  		isValid:    false,
  1083  	}, {
  1084  		name:       "changed data",
  1085  		newHistory: changedData,
  1086  		oldHistory: valid,
  1087  		isValid:    false,
  1088  	}, {
  1089  		name:       "changed revision",
  1090  		newHistory: changedRevision,
  1091  		oldHistory: valid,
  1092  		isValid:    true,
  1093  	},
  1094  	}
  1095  
  1096  	for _, tc := range cases {
  1097  		t.Run(tc.name, func(t *testing.T) {
  1098  			errs := ValidateControllerRevisionUpdate(&tc.newHistory, &tc.oldHistory)
  1099  			if tc.isValid && len(errs) > 0 {
  1100  				t.Errorf("%v: unexpected error: %v", tc.name, errs)
  1101  			}
  1102  			if !tc.isValid && len(errs) == 0 {
  1103  				t.Errorf("%v: unexpected non-error", tc.name)
  1104  			}
  1105  		})
  1106  	}
  1107  }
  1108  
  1109  func TestValidateDaemonSetStatusUpdate(t *testing.T) {
  1110  	type dsUpdateTest struct {
  1111  		old    apps.DaemonSet
  1112  		update apps.DaemonSet
  1113  	}
  1114  
  1115  	successCases := []dsUpdateTest{{
  1116  		old: apps.DaemonSet{
  1117  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1118  			Status: apps.DaemonSetStatus{
  1119  				CurrentNumberScheduled: 1,
  1120  				NumberMisscheduled:     2,
  1121  				DesiredNumberScheduled: 3,
  1122  				NumberReady:            1,
  1123  				UpdatedNumberScheduled: 1,
  1124  				NumberAvailable:        1,
  1125  				NumberUnavailable:      2,
  1126  			},
  1127  		},
  1128  		update: apps.DaemonSet{
  1129  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1130  			Status: apps.DaemonSetStatus{
  1131  				CurrentNumberScheduled: 1,
  1132  				NumberMisscheduled:     1,
  1133  				DesiredNumberScheduled: 3,
  1134  				NumberReady:            1,
  1135  				UpdatedNumberScheduled: 1,
  1136  				NumberAvailable:        1,
  1137  				NumberUnavailable:      2,
  1138  			},
  1139  		},
  1140  	},
  1141  	}
  1142  
  1143  	for _, successCase := range successCases {
  1144  		successCase.old.ObjectMeta.ResourceVersion = "1"
  1145  		successCase.update.ObjectMeta.ResourceVersion = "1"
  1146  		if errs := ValidateDaemonSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
  1147  			t.Errorf("expected success: %v", errs)
  1148  		}
  1149  	}
  1150  	errorCases := map[string]dsUpdateTest{
  1151  		"negative values": {
  1152  			old: apps.DaemonSet{
  1153  				ObjectMeta: metav1.ObjectMeta{
  1154  					Name:            "abc",
  1155  					Namespace:       metav1.NamespaceDefault,
  1156  					ResourceVersion: "10",
  1157  				},
  1158  				Status: apps.DaemonSetStatus{
  1159  					CurrentNumberScheduled: 1,
  1160  					NumberMisscheduled:     2,
  1161  					DesiredNumberScheduled: 3,
  1162  					NumberReady:            1,
  1163  					ObservedGeneration:     3,
  1164  					UpdatedNumberScheduled: 1,
  1165  					NumberAvailable:        1,
  1166  					NumberUnavailable:      2,
  1167  				},
  1168  			},
  1169  			update: apps.DaemonSet{
  1170  				ObjectMeta: metav1.ObjectMeta{
  1171  					Name:            "abc",
  1172  					Namespace:       metav1.NamespaceDefault,
  1173  					ResourceVersion: "10",
  1174  				},
  1175  				Status: apps.DaemonSetStatus{
  1176  					CurrentNumberScheduled: -1,
  1177  					NumberMisscheduled:     -1,
  1178  					DesiredNumberScheduled: -3,
  1179  					NumberReady:            -1,
  1180  					ObservedGeneration:     -3,
  1181  					UpdatedNumberScheduled: -1,
  1182  					NumberAvailable:        -1,
  1183  					NumberUnavailable:      -2,
  1184  				},
  1185  			},
  1186  		},
  1187  		"negative CurrentNumberScheduled": {
  1188  			old: apps.DaemonSet{
  1189  				ObjectMeta: metav1.ObjectMeta{
  1190  					Name:            "abc",
  1191  					Namespace:       metav1.NamespaceDefault,
  1192  					ResourceVersion: "10",
  1193  				},
  1194  				Status: apps.DaemonSetStatus{
  1195  					CurrentNumberScheduled: 1,
  1196  					NumberMisscheduled:     2,
  1197  					DesiredNumberScheduled: 3,
  1198  					NumberReady:            1,
  1199  					ObservedGeneration:     3,
  1200  					UpdatedNumberScheduled: 1,
  1201  					NumberAvailable:        1,
  1202  					NumberUnavailable:      2,
  1203  				},
  1204  			},
  1205  			update: apps.DaemonSet{
  1206  				ObjectMeta: metav1.ObjectMeta{
  1207  					Name:            "abc",
  1208  					Namespace:       metav1.NamespaceDefault,
  1209  					ResourceVersion: "10",
  1210  				},
  1211  				Status: apps.DaemonSetStatus{
  1212  					CurrentNumberScheduled: -1,
  1213  					NumberMisscheduled:     1,
  1214  					DesiredNumberScheduled: 3,
  1215  					NumberReady:            1,
  1216  					ObservedGeneration:     3,
  1217  					UpdatedNumberScheduled: 1,
  1218  					NumberAvailable:        1,
  1219  					NumberUnavailable:      2,
  1220  				},
  1221  			},
  1222  		},
  1223  		"negative NumberMisscheduled": {
  1224  			old: apps.DaemonSet{
  1225  				ObjectMeta: metav1.ObjectMeta{
  1226  					Name:            "abc",
  1227  					Namespace:       metav1.NamespaceDefault,
  1228  					ResourceVersion: "10",
  1229  				},
  1230  				Status: apps.DaemonSetStatus{
  1231  					CurrentNumberScheduled: 1,
  1232  					NumberMisscheduled:     2,
  1233  					DesiredNumberScheduled: 3,
  1234  					NumberReady:            1,
  1235  					ObservedGeneration:     3,
  1236  					UpdatedNumberScheduled: 1,
  1237  					NumberAvailable:        1,
  1238  					NumberUnavailable:      2,
  1239  				},
  1240  			},
  1241  			update: apps.DaemonSet{
  1242  				ObjectMeta: metav1.ObjectMeta{
  1243  					Name:            "abc",
  1244  					Namespace:       metav1.NamespaceDefault,
  1245  					ResourceVersion: "10",
  1246  				},
  1247  				Status: apps.DaemonSetStatus{
  1248  					CurrentNumberScheduled: 1,
  1249  					NumberMisscheduled:     -1,
  1250  					DesiredNumberScheduled: 3,
  1251  					NumberReady:            1,
  1252  					ObservedGeneration:     3,
  1253  					UpdatedNumberScheduled: 1,
  1254  					NumberAvailable:        1,
  1255  					NumberUnavailable:      2,
  1256  				},
  1257  			},
  1258  		},
  1259  		"negative DesiredNumberScheduled": {
  1260  			old: apps.DaemonSet{
  1261  				ObjectMeta: metav1.ObjectMeta{
  1262  					Name:            "abc",
  1263  					Namespace:       metav1.NamespaceDefault,
  1264  					ResourceVersion: "10",
  1265  				},
  1266  				Status: apps.DaemonSetStatus{
  1267  					CurrentNumberScheduled: 1,
  1268  					NumberMisscheduled:     2,
  1269  					DesiredNumberScheduled: 3,
  1270  					NumberReady:            1,
  1271  					ObservedGeneration:     3,
  1272  					UpdatedNumberScheduled: 1,
  1273  					NumberAvailable:        1,
  1274  					NumberUnavailable:      2,
  1275  				},
  1276  			},
  1277  			update: apps.DaemonSet{
  1278  				ObjectMeta: metav1.ObjectMeta{
  1279  					Name:            "abc",
  1280  					Namespace:       metav1.NamespaceDefault,
  1281  					ResourceVersion: "10",
  1282  				},
  1283  				Status: apps.DaemonSetStatus{
  1284  					CurrentNumberScheduled: 1,
  1285  					NumberMisscheduled:     1,
  1286  					DesiredNumberScheduled: -3,
  1287  					NumberReady:            1,
  1288  					ObservedGeneration:     3,
  1289  					UpdatedNumberScheduled: 1,
  1290  					NumberAvailable:        1,
  1291  					NumberUnavailable:      2,
  1292  				},
  1293  			},
  1294  		},
  1295  		"negative NumberReady": {
  1296  			old: apps.DaemonSet{
  1297  				ObjectMeta: metav1.ObjectMeta{
  1298  					Name:            "abc",
  1299  					Namespace:       metav1.NamespaceDefault,
  1300  					ResourceVersion: "10",
  1301  				},
  1302  				Status: apps.DaemonSetStatus{
  1303  					CurrentNumberScheduled: 1,
  1304  					NumberMisscheduled:     2,
  1305  					DesiredNumberScheduled: 3,
  1306  					NumberReady:            1,
  1307  					ObservedGeneration:     3,
  1308  					UpdatedNumberScheduled: 1,
  1309  					NumberAvailable:        1,
  1310  					NumberUnavailable:      2,
  1311  				},
  1312  			},
  1313  			update: apps.DaemonSet{
  1314  				ObjectMeta: metav1.ObjectMeta{
  1315  					Name:            "abc",
  1316  					Namespace:       metav1.NamespaceDefault,
  1317  					ResourceVersion: "10",
  1318  				},
  1319  				Status: apps.DaemonSetStatus{
  1320  					CurrentNumberScheduled: 1,
  1321  					NumberMisscheduled:     1,
  1322  					DesiredNumberScheduled: 3,
  1323  					NumberReady:            -1,
  1324  					ObservedGeneration:     3,
  1325  					UpdatedNumberScheduled: 1,
  1326  					NumberAvailable:        1,
  1327  					NumberUnavailable:      2,
  1328  				},
  1329  			},
  1330  		},
  1331  		"negative ObservedGeneration": {
  1332  			old: apps.DaemonSet{
  1333  				ObjectMeta: metav1.ObjectMeta{
  1334  					Name:            "abc",
  1335  					Namespace:       metav1.NamespaceDefault,
  1336  					ResourceVersion: "10",
  1337  				},
  1338  				Status: apps.DaemonSetStatus{
  1339  					CurrentNumberScheduled: 1,
  1340  					NumberMisscheduled:     2,
  1341  					DesiredNumberScheduled: 3,
  1342  					NumberReady:            1,
  1343  					ObservedGeneration:     3,
  1344  					UpdatedNumberScheduled: 1,
  1345  					NumberAvailable:        1,
  1346  					NumberUnavailable:      2,
  1347  				},
  1348  			},
  1349  			update: apps.DaemonSet{
  1350  				ObjectMeta: metav1.ObjectMeta{
  1351  					Name:            "abc",
  1352  					Namespace:       metav1.NamespaceDefault,
  1353  					ResourceVersion: "10",
  1354  				},
  1355  				Status: apps.DaemonSetStatus{
  1356  					CurrentNumberScheduled: 1,
  1357  					NumberMisscheduled:     1,
  1358  					DesiredNumberScheduled: 3,
  1359  					NumberReady:            1,
  1360  					ObservedGeneration:     -3,
  1361  					UpdatedNumberScheduled: 1,
  1362  					NumberAvailable:        1,
  1363  					NumberUnavailable:      2,
  1364  				},
  1365  			},
  1366  		},
  1367  		"negative UpdatedNumberScheduled": {
  1368  			old: apps.DaemonSet{
  1369  				ObjectMeta: metav1.ObjectMeta{
  1370  					Name:            "abc",
  1371  					Namespace:       metav1.NamespaceDefault,
  1372  					ResourceVersion: "10",
  1373  				},
  1374  				Status: apps.DaemonSetStatus{
  1375  					CurrentNumberScheduled: 1,
  1376  					NumberMisscheduled:     2,
  1377  					DesiredNumberScheduled: 3,
  1378  					NumberReady:            1,
  1379  					ObservedGeneration:     3,
  1380  					UpdatedNumberScheduled: 1,
  1381  					NumberAvailable:        1,
  1382  					NumberUnavailable:      2,
  1383  				},
  1384  			},
  1385  			update: apps.DaemonSet{
  1386  				ObjectMeta: metav1.ObjectMeta{
  1387  					Name:            "abc",
  1388  					Namespace:       metav1.NamespaceDefault,
  1389  					ResourceVersion: "10",
  1390  				},
  1391  				Status: apps.DaemonSetStatus{
  1392  					CurrentNumberScheduled: 1,
  1393  					NumberMisscheduled:     1,
  1394  					DesiredNumberScheduled: 3,
  1395  					NumberReady:            1,
  1396  					ObservedGeneration:     3,
  1397  					UpdatedNumberScheduled: -1,
  1398  					NumberAvailable:        1,
  1399  					NumberUnavailable:      2,
  1400  				},
  1401  			},
  1402  		},
  1403  		"negative NumberAvailable": {
  1404  			old: apps.DaemonSet{
  1405  				ObjectMeta: metav1.ObjectMeta{
  1406  					Name:            "abc",
  1407  					Namespace:       metav1.NamespaceDefault,
  1408  					ResourceVersion: "10",
  1409  				},
  1410  				Status: apps.DaemonSetStatus{
  1411  					CurrentNumberScheduled: 1,
  1412  					NumberMisscheduled:     2,
  1413  					DesiredNumberScheduled: 3,
  1414  					NumberReady:            1,
  1415  					ObservedGeneration:     3,
  1416  					UpdatedNumberScheduled: 1,
  1417  					NumberAvailable:        1,
  1418  					NumberUnavailable:      2,
  1419  				},
  1420  			},
  1421  			update: apps.DaemonSet{
  1422  				ObjectMeta: metav1.ObjectMeta{
  1423  					Name:            "abc",
  1424  					Namespace:       metav1.NamespaceDefault,
  1425  					ResourceVersion: "10",
  1426  				},
  1427  				Status: apps.DaemonSetStatus{
  1428  					CurrentNumberScheduled: 1,
  1429  					NumberMisscheduled:     1,
  1430  					DesiredNumberScheduled: 3,
  1431  					NumberReady:            1,
  1432  					ObservedGeneration:     3,
  1433  					UpdatedNumberScheduled: 1,
  1434  					NumberAvailable:        -1,
  1435  					NumberUnavailable:      2,
  1436  				},
  1437  			},
  1438  		},
  1439  		"negative NumberUnavailable": {
  1440  			old: apps.DaemonSet{
  1441  				ObjectMeta: metav1.ObjectMeta{
  1442  					Name:            "abc",
  1443  					Namespace:       metav1.NamespaceDefault,
  1444  					ResourceVersion: "10",
  1445  				},
  1446  				Status: apps.DaemonSetStatus{
  1447  					CurrentNumberScheduled: 1,
  1448  					NumberMisscheduled:     2,
  1449  					DesiredNumberScheduled: 3,
  1450  					NumberReady:            1,
  1451  					ObservedGeneration:     3,
  1452  					UpdatedNumberScheduled: 1,
  1453  					NumberAvailable:        1,
  1454  					NumberUnavailable:      2,
  1455  				},
  1456  			},
  1457  			update: apps.DaemonSet{
  1458  				ObjectMeta: metav1.ObjectMeta{
  1459  					Name:            "abc",
  1460  					Namespace:       metav1.NamespaceDefault,
  1461  					ResourceVersion: "10",
  1462  				},
  1463  				Status: apps.DaemonSetStatus{
  1464  					CurrentNumberScheduled: 1,
  1465  					NumberMisscheduled:     1,
  1466  					DesiredNumberScheduled: 3,
  1467  					NumberReady:            1,
  1468  					ObservedGeneration:     3,
  1469  					UpdatedNumberScheduled: 1,
  1470  					NumberAvailable:        1,
  1471  					NumberUnavailable:      -2,
  1472  				},
  1473  			},
  1474  		},
  1475  	}
  1476  
  1477  	for testName, errorCase := range errorCases {
  1478  		if errs := ValidateDaemonSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
  1479  			t.Errorf("expected failure: %s", testName)
  1480  		}
  1481  	}
  1482  }
  1483  
  1484  func TestValidateDaemonSetUpdate(t *testing.T) {
  1485  	validSelector := map[string]string{"a": "b"}
  1486  	validSelector2 := map[string]string{"c": "d"}
  1487  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  1488  
  1489  	validPodSpecAbc := api.PodSpec{
  1490  		RestartPolicy: api.RestartPolicyAlways,
  1491  		DNSPolicy:     api.DNSClusterFirst,
  1492  		Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1493  	}
  1494  	validPodSpecDef := api.PodSpec{
  1495  		RestartPolicy: api.RestartPolicyAlways,
  1496  		DNSPolicy:     api.DNSClusterFirst,
  1497  		Containers:    []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1498  	}
  1499  	validPodSpecNodeSelector := api.PodSpec{
  1500  		NodeSelector:  validSelector,
  1501  		NodeName:      "xyz",
  1502  		RestartPolicy: api.RestartPolicyAlways,
  1503  		DNSPolicy:     api.DNSClusterFirst,
  1504  		Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1505  	}
  1506  	validPodSpecVolume := api.PodSpec{
  1507  		Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  1508  		RestartPolicy: api.RestartPolicyAlways,
  1509  		DNSPolicy:     api.DNSClusterFirst,
  1510  		Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1511  	}
  1512  
  1513  	validPodTemplateAbc := api.PodTemplate{
  1514  		Template: api.PodTemplateSpec{
  1515  			ObjectMeta: metav1.ObjectMeta{
  1516  				Labels: validSelector,
  1517  			},
  1518  			Spec: validPodSpecAbc,
  1519  		},
  1520  	}
  1521  	validPodTemplateAbcSemanticallyEqual := api.PodTemplate{
  1522  		Template: api.PodTemplateSpec{
  1523  			ObjectMeta: metav1.ObjectMeta{
  1524  				Labels: validSelector,
  1525  			},
  1526  			Spec: validPodSpecAbc,
  1527  		},
  1528  	}
  1529  	validPodTemplateAbcSemanticallyEqual.Template.Spec.ImagePullSecrets = []api.LocalObjectReference{}
  1530  	validPodTemplateNodeSelector := api.PodTemplate{
  1531  		Template: api.PodTemplateSpec{
  1532  			ObjectMeta: metav1.ObjectMeta{
  1533  				Labels: validSelector,
  1534  			},
  1535  			Spec: validPodSpecNodeSelector,
  1536  		},
  1537  	}
  1538  	validPodTemplateAbc2 := api.PodTemplate{
  1539  		Template: api.PodTemplateSpec{
  1540  			ObjectMeta: metav1.ObjectMeta{
  1541  				Labels: validSelector2,
  1542  			},
  1543  			Spec: validPodSpecAbc,
  1544  		},
  1545  	}
  1546  	validPodTemplateDef := api.PodTemplate{
  1547  		Template: api.PodTemplateSpec{
  1548  			ObjectMeta: metav1.ObjectMeta{
  1549  				Labels: validSelector2,
  1550  			},
  1551  			Spec: validPodSpecDef,
  1552  		},
  1553  	}
  1554  	invalidPodTemplate := api.PodTemplate{
  1555  		Template: api.PodTemplateSpec{
  1556  			Spec: api.PodSpec{
  1557  				// no containers specified
  1558  				RestartPolicy: api.RestartPolicyAlways,
  1559  				DNSPolicy:     api.DNSClusterFirst,
  1560  			},
  1561  			ObjectMeta: metav1.ObjectMeta{
  1562  				Labels: validSelector,
  1563  			},
  1564  		},
  1565  	}
  1566  	readWriteVolumePodTemplate := api.PodTemplate{
  1567  		Template: api.PodTemplateSpec{
  1568  			ObjectMeta: metav1.ObjectMeta{
  1569  				Labels: validSelector,
  1570  			},
  1571  			Spec: validPodSpecVolume,
  1572  		},
  1573  	}
  1574  	type dsUpdateTest struct {
  1575  		old                             apps.DaemonSet
  1576  		update                          apps.DaemonSet
  1577  		expectedErrNum                  int
  1578  		enableSkipReadOnlyValidationGCE bool
  1579  	}
  1580  	successCases := map[string]dsUpdateTest{
  1581  		"no change": {
  1582  			old: apps.DaemonSet{
  1583  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1584  				Spec: apps.DaemonSetSpec{
  1585  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1586  					TemplateGeneration: 1,
  1587  					Template:           validPodTemplateAbc.Template,
  1588  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1589  						Type: apps.OnDeleteDaemonSetStrategyType,
  1590  					},
  1591  				},
  1592  			},
  1593  			update: apps.DaemonSet{
  1594  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1595  				Spec: apps.DaemonSetSpec{
  1596  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1597  					TemplateGeneration: 1,
  1598  					Template:           validPodTemplateAbc.Template,
  1599  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1600  						Type: apps.OnDeleteDaemonSetStrategyType,
  1601  					},
  1602  				},
  1603  			},
  1604  		},
  1605  		"change template and selector": {
  1606  			old: apps.DaemonSet{
  1607  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1608  				Spec: apps.DaemonSetSpec{
  1609  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1610  					TemplateGeneration: 2,
  1611  					Template:           validPodTemplateAbc.Template,
  1612  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1613  						Type: apps.OnDeleteDaemonSetStrategyType,
  1614  					},
  1615  				},
  1616  			},
  1617  			update: apps.DaemonSet{
  1618  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1619  				Spec: apps.DaemonSetSpec{
  1620  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector2},
  1621  					TemplateGeneration: 3,
  1622  					Template:           validPodTemplateAbc2.Template,
  1623  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1624  						Type: apps.OnDeleteDaemonSetStrategyType,
  1625  					},
  1626  				},
  1627  			},
  1628  		},
  1629  		"change template": {
  1630  			old: apps.DaemonSet{
  1631  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1632  				Spec: apps.DaemonSetSpec{
  1633  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1634  					TemplateGeneration: 3,
  1635  					Template:           validPodTemplateAbc.Template,
  1636  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1637  						Type: apps.OnDeleteDaemonSetStrategyType,
  1638  					},
  1639  				},
  1640  			},
  1641  			update: apps.DaemonSet{
  1642  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1643  				Spec: apps.DaemonSetSpec{
  1644  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1645  					TemplateGeneration: 4,
  1646  					Template:           validPodTemplateNodeSelector.Template,
  1647  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1648  						Type: apps.OnDeleteDaemonSetStrategyType,
  1649  					},
  1650  				},
  1651  			},
  1652  		},
  1653  		"change container image name": {
  1654  			old: apps.DaemonSet{
  1655  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1656  				Spec: apps.DaemonSetSpec{
  1657  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1658  					TemplateGeneration: 1,
  1659  					Template:           validPodTemplateAbc.Template,
  1660  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1661  						Type: apps.OnDeleteDaemonSetStrategyType,
  1662  					},
  1663  				},
  1664  			},
  1665  			update: apps.DaemonSet{
  1666  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1667  				Spec: apps.DaemonSetSpec{
  1668  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector2},
  1669  					TemplateGeneration: 2,
  1670  					Template:           validPodTemplateDef.Template,
  1671  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1672  						Type: apps.OnDeleteDaemonSetStrategyType,
  1673  					},
  1674  				},
  1675  			},
  1676  		},
  1677  		"change update strategy": {
  1678  			old: apps.DaemonSet{
  1679  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1680  				Spec: apps.DaemonSetSpec{
  1681  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1682  					TemplateGeneration: 4,
  1683  					Template:           validPodTemplateAbc.Template,
  1684  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1685  						Type: apps.OnDeleteDaemonSetStrategyType,
  1686  					},
  1687  				},
  1688  			},
  1689  			update: apps.DaemonSet{
  1690  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1691  				Spec: apps.DaemonSetSpec{
  1692  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1693  					TemplateGeneration: 4,
  1694  					Template:           validPodTemplateAbc.Template,
  1695  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1696  						Type: apps.RollingUpdateDaemonSetStrategyType,
  1697  						RollingUpdate: &apps.RollingUpdateDaemonSet{
  1698  							MaxUnavailable: intstr.FromInt32(1),
  1699  						},
  1700  					},
  1701  				},
  1702  			},
  1703  		},
  1704  		"unchanged templateGeneration upon semantically equal template update": {
  1705  			old: apps.DaemonSet{
  1706  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1707  				Spec: apps.DaemonSetSpec{
  1708  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1709  					TemplateGeneration: 4,
  1710  					Template:           validPodTemplateAbc.Template,
  1711  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1712  						Type: apps.OnDeleteDaemonSetStrategyType,
  1713  					},
  1714  				},
  1715  			},
  1716  			update: apps.DaemonSet{
  1717  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1718  				Spec: apps.DaemonSetSpec{
  1719  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1720  					TemplateGeneration: 4,
  1721  					Template:           validPodTemplateAbcSemanticallyEqual.Template,
  1722  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1723  						Type: apps.RollingUpdateDaemonSetStrategyType,
  1724  						RollingUpdate: &apps.RollingUpdateDaemonSet{
  1725  							MaxUnavailable: intstr.FromInt32(1),
  1726  						},
  1727  					},
  1728  				},
  1729  			},
  1730  		},
  1731  		"Read-write volume verification": {
  1732  			enableSkipReadOnlyValidationGCE: true,
  1733  			old: apps.DaemonSet{
  1734  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1735  				Spec: apps.DaemonSetSpec{
  1736  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1737  					TemplateGeneration: 1,
  1738  					Template:           validPodTemplateAbc.Template,
  1739  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1740  						Type: apps.OnDeleteDaemonSetStrategyType,
  1741  					},
  1742  				},
  1743  			},
  1744  			update: apps.DaemonSet{
  1745  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1746  				Spec: apps.DaemonSetSpec{
  1747  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1748  					TemplateGeneration: 2,
  1749  					Template:           readWriteVolumePodTemplate.Template,
  1750  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1751  						Type: apps.OnDeleteDaemonSetStrategyType,
  1752  					},
  1753  				},
  1754  			},
  1755  		},
  1756  	}
  1757  	for testName, successCase := range successCases {
  1758  		t.Run(testName, func(t *testing.T) {
  1759  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, successCase.enableSkipReadOnlyValidationGCE)()
  1760  			// ResourceVersion is required for updates.
  1761  			successCase.old.ObjectMeta.ResourceVersion = "1"
  1762  			successCase.update.ObjectMeta.ResourceVersion = "2"
  1763  			// Check test setup
  1764  			if successCase.expectedErrNum > 0 {
  1765  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
  1766  			}
  1767  			if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
  1768  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  1769  			}
  1770  			if errs := ValidateDaemonSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  1771  				t.Errorf("%q expected no error, but got: %v", testName, errs)
  1772  			}
  1773  		})
  1774  	}
  1775  	errorCases := map[string]dsUpdateTest{
  1776  		"change daemon name": {
  1777  			old: apps.DaemonSet{
  1778  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  1779  				Spec: apps.DaemonSetSpec{
  1780  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1781  					TemplateGeneration: 1,
  1782  					Template:           validPodTemplateAbc.Template,
  1783  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1784  						Type: apps.OnDeleteDaemonSetStrategyType,
  1785  					},
  1786  				},
  1787  			},
  1788  			update: apps.DaemonSet{
  1789  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1790  				Spec: apps.DaemonSetSpec{
  1791  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1792  					TemplateGeneration: 1,
  1793  					Template:           validPodTemplateAbc.Template,
  1794  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1795  						Type: apps.OnDeleteDaemonSetStrategyType,
  1796  					},
  1797  				},
  1798  			},
  1799  			expectedErrNum: 1,
  1800  		},
  1801  		"invalid selector": {
  1802  			old: apps.DaemonSet{
  1803  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1804  				Spec: apps.DaemonSetSpec{
  1805  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1806  					TemplateGeneration: 1,
  1807  					Template:           validPodTemplateAbc.Template,
  1808  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1809  						Type: apps.OnDeleteDaemonSetStrategyType,
  1810  					},
  1811  				},
  1812  			},
  1813  			update: apps.DaemonSet{
  1814  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1815  				Spec: apps.DaemonSetSpec{
  1816  					Selector:           &metav1.LabelSelector{MatchLabels: invalidSelector},
  1817  					TemplateGeneration: 1,
  1818  					Template:           validPodTemplateAbc.Template,
  1819  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1820  						Type: apps.OnDeleteDaemonSetStrategyType,
  1821  					},
  1822  				},
  1823  			},
  1824  			expectedErrNum: 1,
  1825  		},
  1826  		"invalid pod": {
  1827  			old: apps.DaemonSet{
  1828  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1829  				Spec: apps.DaemonSetSpec{
  1830  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1831  					TemplateGeneration: 1,
  1832  					Template:           validPodTemplateAbc.Template,
  1833  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1834  						Type: apps.OnDeleteDaemonSetStrategyType,
  1835  					},
  1836  				},
  1837  			},
  1838  			update: apps.DaemonSet{
  1839  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1840  				Spec: apps.DaemonSetSpec{
  1841  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1842  					TemplateGeneration: 2,
  1843  					Template:           invalidPodTemplate.Template,
  1844  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1845  						Type: apps.OnDeleteDaemonSetStrategyType,
  1846  					},
  1847  				},
  1848  			},
  1849  			expectedErrNum: 1,
  1850  		},
  1851  		"invalid read-write volume": {
  1852  			enableSkipReadOnlyValidationGCE: false,
  1853  			old: apps.DaemonSet{
  1854  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1855  				Spec: apps.DaemonSetSpec{
  1856  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1857  					TemplateGeneration: 1,
  1858  					Template:           validPodTemplateAbc.Template,
  1859  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1860  						Type: apps.OnDeleteDaemonSetStrategyType,
  1861  					},
  1862  				},
  1863  			},
  1864  			update: apps.DaemonSet{
  1865  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1866  				Spec: apps.DaemonSetSpec{
  1867  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1868  					TemplateGeneration: 2,
  1869  					Template:           readWriteVolumePodTemplate.Template,
  1870  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1871  						Type: apps.OnDeleteDaemonSetStrategyType,
  1872  					},
  1873  				},
  1874  			},
  1875  			expectedErrNum: 1,
  1876  		},
  1877  		"invalid update strategy": {
  1878  			old: apps.DaemonSet{
  1879  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1880  				Spec: apps.DaemonSetSpec{
  1881  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1882  					TemplateGeneration: 1,
  1883  					Template:           validPodTemplateAbc.Template,
  1884  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1885  						Type: apps.OnDeleteDaemonSetStrategyType,
  1886  					},
  1887  				},
  1888  			},
  1889  			update: apps.DaemonSet{
  1890  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1891  				Spec: apps.DaemonSetSpec{
  1892  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1893  					TemplateGeneration: 1,
  1894  					Template:           validPodTemplateAbc.Template,
  1895  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1896  						Type: "Random",
  1897  					},
  1898  				},
  1899  			},
  1900  			expectedErrNum: 1,
  1901  		},
  1902  		"negative templateGeneration": {
  1903  			old: apps.DaemonSet{
  1904  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1905  				Spec: apps.DaemonSetSpec{
  1906  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1907  					TemplateGeneration: -1,
  1908  					Template:           validPodTemplateAbc.Template,
  1909  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1910  						Type: apps.OnDeleteDaemonSetStrategyType,
  1911  					},
  1912  				},
  1913  			},
  1914  			update: apps.DaemonSet{
  1915  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1916  				Spec: apps.DaemonSetSpec{
  1917  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1918  					TemplateGeneration: -1,
  1919  					Template:           validPodTemplateAbc.Template,
  1920  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1921  						Type: apps.OnDeleteDaemonSetStrategyType,
  1922  					},
  1923  				},
  1924  			},
  1925  			expectedErrNum: 1,
  1926  		},
  1927  		"decreased templateGeneration": {
  1928  			old: apps.DaemonSet{
  1929  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1930  				Spec: apps.DaemonSetSpec{
  1931  					TemplateGeneration: 2,
  1932  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1933  					Template:           validPodTemplateAbc.Template,
  1934  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1935  						Type: apps.OnDeleteDaemonSetStrategyType,
  1936  					},
  1937  				},
  1938  			},
  1939  			update: apps.DaemonSet{
  1940  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1941  				Spec: apps.DaemonSetSpec{
  1942  					TemplateGeneration: 1,
  1943  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1944  					Template:           validPodTemplateAbc.Template,
  1945  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1946  						Type: apps.OnDeleteDaemonSetStrategyType,
  1947  					},
  1948  				},
  1949  			},
  1950  			expectedErrNum: 1,
  1951  		},
  1952  		"unchanged templateGeneration upon template update": {
  1953  			old: apps.DaemonSet{
  1954  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1955  				Spec: apps.DaemonSetSpec{
  1956  					TemplateGeneration: 2,
  1957  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1958  					Template:           validPodTemplateAbc.Template,
  1959  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1960  						Type: apps.OnDeleteDaemonSetStrategyType,
  1961  					},
  1962  				},
  1963  			},
  1964  			update: apps.DaemonSet{
  1965  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1966  				Spec: apps.DaemonSetSpec{
  1967  					TemplateGeneration: 2,
  1968  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector2},
  1969  					Template:           validPodTemplateAbc2.Template,
  1970  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1971  						Type: apps.OnDeleteDaemonSetStrategyType,
  1972  					},
  1973  				},
  1974  			},
  1975  			expectedErrNum: 1,
  1976  		},
  1977  	}
  1978  	for testName, errorCase := range errorCases {
  1979  		t.Run(testName, func(t *testing.T) {
  1980  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, errorCase.enableSkipReadOnlyValidationGCE)()
  1981  			// ResourceVersion is required for updates.
  1982  			errorCase.old.ObjectMeta.ResourceVersion = "1"
  1983  			errorCase.update.ObjectMeta.ResourceVersion = "2"
  1984  			// Check test setup
  1985  			if errorCase.expectedErrNum <= 0 {
  1986  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
  1987  			}
  1988  			if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
  1989  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  1990  			}
  1991  			// Run the tests
  1992  			if errs := ValidateDaemonSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
  1993  				t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
  1994  			} else {
  1995  				t.Logf("(PASS) %q got errors %v", testName, errs)
  1996  			}
  1997  		})
  1998  	}
  1999  }
  2000  
  2001  func TestValidateDaemonSet(t *testing.T) {
  2002  	validSelector := map[string]string{"a": "b"}
  2003  	validPodTemplate := api.PodTemplate{
  2004  		Template: api.PodTemplateSpec{
  2005  			ObjectMeta: metav1.ObjectMeta{
  2006  				Labels: validSelector,
  2007  			},
  2008  			Spec: api.PodSpec{
  2009  				RestartPolicy: api.RestartPolicyAlways,
  2010  				DNSPolicy:     api.DNSClusterFirst,
  2011  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2012  			},
  2013  		},
  2014  	}
  2015  	validHostNetPodTemplate := api.PodTemplate{
  2016  		Template: api.PodTemplateSpec{
  2017  			ObjectMeta: metav1.ObjectMeta{
  2018  				Labels: validSelector,
  2019  			},
  2020  			Spec: api.PodSpec{
  2021  				SecurityContext: &api.PodSecurityContext{
  2022  					HostNetwork: true,
  2023  				},
  2024  				RestartPolicy: api.RestartPolicyAlways,
  2025  				DNSPolicy:     api.DNSClusterFirst,
  2026  				Containers: []api.Container{{
  2027  					Name:                     "abc",
  2028  					Image:                    "image",
  2029  					ImagePullPolicy:          "IfNotPresent",
  2030  					TerminationMessagePolicy: api.TerminationMessageReadFile,
  2031  					Ports: []api.ContainerPort{{
  2032  						ContainerPort: 12345,
  2033  						Protocol:      api.ProtocolTCP,
  2034  					}},
  2035  				}},
  2036  			},
  2037  		},
  2038  	}
  2039  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  2040  	invalidPodTemplate := api.PodTemplate{
  2041  		Template: api.PodTemplateSpec{
  2042  			Spec: api.PodSpec{
  2043  				RestartPolicy: api.RestartPolicyAlways,
  2044  				DNSPolicy:     api.DNSClusterFirst,
  2045  			},
  2046  			ObjectMeta: metav1.ObjectMeta{
  2047  				Labels: invalidSelector,
  2048  			},
  2049  		},
  2050  	}
  2051  	successCases := []apps.DaemonSet{{
  2052  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2053  		Spec: apps.DaemonSetSpec{
  2054  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2055  			Template: validPodTemplate.Template,
  2056  			UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2057  				Type: apps.OnDeleteDaemonSetStrategyType,
  2058  			},
  2059  		},
  2060  	}, {
  2061  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
  2062  		Spec: apps.DaemonSetSpec{
  2063  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2064  			Template: validPodTemplate.Template,
  2065  			UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2066  				Type: apps.OnDeleteDaemonSetStrategyType,
  2067  			},
  2068  		},
  2069  	}, {
  2070  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
  2071  		Spec: apps.DaemonSetSpec{
  2072  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2073  			Template: validHostNetPodTemplate.Template,
  2074  			UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2075  				Type: apps.OnDeleteDaemonSetStrategyType,
  2076  			},
  2077  		},
  2078  	},
  2079  	}
  2080  	for _, successCase := range successCases {
  2081  		if errs := ValidateDaemonSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2082  			t.Errorf("expected success: %v", errs)
  2083  		}
  2084  	}
  2085  
  2086  	errorCases := map[string]apps.DaemonSet{
  2087  		"zero-length ID": {
  2088  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2089  			Spec: apps.DaemonSetSpec{
  2090  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2091  				Template: validPodTemplate.Template,
  2092  			},
  2093  		},
  2094  		"missing-namespace": {
  2095  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
  2096  			Spec: apps.DaemonSetSpec{
  2097  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2098  				Template: validPodTemplate.Template,
  2099  			},
  2100  		},
  2101  		"nil selector": {
  2102  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2103  			Spec: apps.DaemonSetSpec{
  2104  				Template: validPodTemplate.Template,
  2105  			},
  2106  		},
  2107  		"empty selector": {
  2108  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2109  			Spec: apps.DaemonSetSpec{
  2110  				Selector: &metav1.LabelSelector{},
  2111  				Template: validPodTemplate.Template,
  2112  			},
  2113  		},
  2114  		"selector_doesnt_match": {
  2115  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2116  			Spec: apps.DaemonSetSpec{
  2117  				Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  2118  				Template: validPodTemplate.Template,
  2119  			},
  2120  		},
  2121  		"invalid template": {
  2122  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2123  			Spec: apps.DaemonSetSpec{
  2124  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2125  			},
  2126  		},
  2127  		"invalid_label": {
  2128  			ObjectMeta: metav1.ObjectMeta{
  2129  				Name:      "abc-123",
  2130  				Namespace: metav1.NamespaceDefault,
  2131  				Labels: map[string]string{
  2132  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  2133  				},
  2134  			},
  2135  			Spec: apps.DaemonSetSpec{
  2136  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2137  				Template: validPodTemplate.Template,
  2138  			},
  2139  		},
  2140  		"invalid_label 2": {
  2141  			ObjectMeta: metav1.ObjectMeta{
  2142  				Name:      "abc-123",
  2143  				Namespace: metav1.NamespaceDefault,
  2144  				Labels: map[string]string{
  2145  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  2146  				},
  2147  			},
  2148  			Spec: apps.DaemonSetSpec{
  2149  				Template: invalidPodTemplate.Template,
  2150  			},
  2151  		},
  2152  		"invalid_annotation": {
  2153  			ObjectMeta: metav1.ObjectMeta{
  2154  				Name:      "abc-123",
  2155  				Namespace: metav1.NamespaceDefault,
  2156  				Annotations: map[string]string{
  2157  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  2158  				},
  2159  			},
  2160  			Spec: apps.DaemonSetSpec{
  2161  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2162  				Template: validPodTemplate.Template,
  2163  			},
  2164  		},
  2165  		"invalid restart policy 1": {
  2166  			ObjectMeta: metav1.ObjectMeta{
  2167  				Name:      "abc-123",
  2168  				Namespace: metav1.NamespaceDefault,
  2169  			},
  2170  			Spec: apps.DaemonSetSpec{
  2171  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2172  				Template: api.PodTemplateSpec{
  2173  					Spec: api.PodSpec{
  2174  						RestartPolicy: api.RestartPolicyOnFailure,
  2175  						DNSPolicy:     api.DNSClusterFirst,
  2176  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2177  					},
  2178  					ObjectMeta: metav1.ObjectMeta{
  2179  						Labels: validSelector,
  2180  					},
  2181  				},
  2182  			},
  2183  		},
  2184  		"invalid restart policy 2": {
  2185  			ObjectMeta: metav1.ObjectMeta{
  2186  				Name:      "abc-123",
  2187  				Namespace: metav1.NamespaceDefault,
  2188  			},
  2189  			Spec: apps.DaemonSetSpec{
  2190  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2191  				Template: api.PodTemplateSpec{
  2192  					Spec: api.PodSpec{
  2193  						RestartPolicy: api.RestartPolicyNever,
  2194  						DNSPolicy:     api.DNSClusterFirst,
  2195  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2196  					},
  2197  					ObjectMeta: metav1.ObjectMeta{
  2198  						Labels: validSelector,
  2199  					},
  2200  				},
  2201  			},
  2202  		},
  2203  		"template may not contain ephemeral containers": {
  2204  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2205  			Spec: apps.DaemonSetSpec{
  2206  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2207  				Template: api.PodTemplateSpec{
  2208  					ObjectMeta: metav1.ObjectMeta{
  2209  						Labels: validSelector,
  2210  					},
  2211  					Spec: api.PodSpec{
  2212  						RestartPolicy:       api.RestartPolicyAlways,
  2213  						DNSPolicy:           api.DNSClusterFirst,
  2214  						Containers:          []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2215  						EphemeralContainers: []api.EphemeralContainer{{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
  2216  					},
  2217  				},
  2218  				UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2219  					Type: apps.OnDeleteDaemonSetStrategyType,
  2220  				},
  2221  			},
  2222  		},
  2223  	}
  2224  	for k, v := range errorCases {
  2225  		errs := ValidateDaemonSet(&v, corevalidation.PodValidationOptions{})
  2226  		if len(errs) == 0 {
  2227  			t.Errorf("expected failure for %s", k)
  2228  		}
  2229  		for i := range errs {
  2230  			field := errs[i].Field
  2231  			if !strings.HasPrefix(field, "spec.template.") &&
  2232  				!strings.HasPrefix(field, "spec.updateStrategy") &&
  2233  				field != "metadata.name" &&
  2234  				field != "metadata.namespace" &&
  2235  				field != "spec.selector" &&
  2236  				field != "spec.template" &&
  2237  				field != "GCEPersistentDisk.ReadOnly" &&
  2238  				field != "spec.template.labels" &&
  2239  				field != "metadata.annotations" &&
  2240  				field != "metadata.labels" {
  2241  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
  2242  			}
  2243  		}
  2244  	}
  2245  }
  2246  
  2247  func validDeployment(tweaks ...func(d *apps.Deployment)) *apps.Deployment {
  2248  	d := &apps.Deployment{
  2249  		ObjectMeta: metav1.ObjectMeta{
  2250  			Name:      "abc",
  2251  			Namespace: metav1.NamespaceDefault,
  2252  		},
  2253  		Spec: apps.DeploymentSpec{
  2254  			Selector: &metav1.LabelSelector{
  2255  				MatchLabels: map[string]string{
  2256  					"name": "abc",
  2257  				},
  2258  			},
  2259  			Strategy: apps.DeploymentStrategy{
  2260  				Type: apps.RollingUpdateDeploymentStrategyType,
  2261  				RollingUpdate: &apps.RollingUpdateDeployment{
  2262  					MaxSurge:       intstr.FromInt32(1),
  2263  					MaxUnavailable: intstr.FromInt32(1),
  2264  				},
  2265  			},
  2266  			Template: api.PodTemplateSpec{
  2267  				ObjectMeta: metav1.ObjectMeta{
  2268  					Name:      "abc",
  2269  					Namespace: metav1.NamespaceDefault,
  2270  					Labels: map[string]string{
  2271  						"name": "abc",
  2272  					},
  2273  				},
  2274  				Spec: api.PodSpec{
  2275  					RestartPolicy: api.RestartPolicyAlways,
  2276  					DNSPolicy:     api.DNSDefault,
  2277  					Containers: []api.Container{{
  2278  						Name:                     "nginx",
  2279  						Image:                    "image",
  2280  						ImagePullPolicy:          api.PullNever,
  2281  						TerminationMessagePolicy: api.TerminationMessageReadFile,
  2282  					}},
  2283  				},
  2284  			},
  2285  			RollbackTo: &apps.RollbackConfig{
  2286  				Revision: 1,
  2287  			},
  2288  		},
  2289  	}
  2290  
  2291  	for _, tweak := range tweaks {
  2292  		tweak(d)
  2293  	}
  2294  
  2295  	return d
  2296  }
  2297  
  2298  func TestValidateDeployment(t *testing.T) {
  2299  	successCases := []*apps.Deployment{
  2300  		validDeployment(),
  2301  		validDeployment(func(d *apps.Deployment) {
  2302  			d.Spec.Template.Spec.SecurityContext = &api.PodSecurityContext{
  2303  				HostNetwork: true,
  2304  			}
  2305  			d.Spec.Template.Spec.Containers[0].Ports = []api.ContainerPort{{
  2306  				ContainerPort: 12345,
  2307  				Protocol:      api.ProtocolTCP,
  2308  			}}
  2309  		}),
  2310  	}
  2311  	for _, successCase := range successCases {
  2312  		if errs := ValidateDeployment(successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2313  			t.Errorf("expected success: %v", errs)
  2314  		}
  2315  	}
  2316  
  2317  	errorCases := map[string]*apps.Deployment{}
  2318  	errorCases["metadata.name: Required value"] = &apps.Deployment{
  2319  		ObjectMeta: metav1.ObjectMeta{
  2320  			Namespace: metav1.NamespaceDefault,
  2321  		},
  2322  	}
  2323  	// selector should match the labels in pod template.
  2324  	invalidSelectorDeployment := validDeployment()
  2325  	invalidSelectorDeployment.Spec.Selector = &metav1.LabelSelector{
  2326  		MatchLabels: map[string]string{
  2327  			"name": "def",
  2328  		},
  2329  	}
  2330  	errorCases["`selector` does not match template `labels`"] = invalidSelectorDeployment
  2331  
  2332  	// RestartPolicy should be always.
  2333  	invalidRestartPolicyDeployment := validDeployment()
  2334  	invalidRestartPolicyDeployment.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever
  2335  	errorCases["Unsupported value: \"Never\""] = invalidRestartPolicyDeployment
  2336  
  2337  	// must have valid strategy type
  2338  	invalidStrategyDeployment := validDeployment()
  2339  	invalidStrategyDeployment.Spec.Strategy.Type = apps.DeploymentStrategyType("randomType")
  2340  	errorCases[`supported values: "Recreate", "RollingUpdate"`] = invalidStrategyDeployment
  2341  
  2342  	// rollingUpdate should be nil for recreate.
  2343  	invalidRecreateDeployment := validDeployment()
  2344  	invalidRecreateDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2345  		Type:          apps.RecreateDeploymentStrategyType,
  2346  		RollingUpdate: &apps.RollingUpdateDeployment{},
  2347  	}
  2348  	errorCases["may not be specified when strategy `type` is 'Recreate'"] = invalidRecreateDeployment
  2349  
  2350  	// MaxSurge should be in the form of 20%.
  2351  	invalidMaxSurgeDeployment := validDeployment()
  2352  	invalidMaxSurgeDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2353  		Type: apps.RollingUpdateDeploymentStrategyType,
  2354  		RollingUpdate: &apps.RollingUpdateDeployment{
  2355  			MaxSurge: intstr.FromString("20Percent"),
  2356  		},
  2357  	}
  2358  	errorCases["a valid percent string must be"] = invalidMaxSurgeDeployment
  2359  
  2360  	// MaxSurge and MaxUnavailable cannot both be zero.
  2361  	invalidRollingUpdateDeployment := validDeployment()
  2362  	invalidRollingUpdateDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2363  		Type: apps.RollingUpdateDeploymentStrategyType,
  2364  		RollingUpdate: &apps.RollingUpdateDeployment{
  2365  			MaxSurge:       intstr.FromString("0%"),
  2366  			MaxUnavailable: intstr.FromInt32(0),
  2367  		},
  2368  	}
  2369  	errorCases["may not be 0 when `maxSurge` is 0"] = invalidRollingUpdateDeployment
  2370  
  2371  	// MaxUnavailable should not be more than 100%.
  2372  	invalidMaxUnavailableDeployment := validDeployment()
  2373  	invalidMaxUnavailableDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2374  		Type: apps.RollingUpdateDeploymentStrategyType,
  2375  		RollingUpdate: &apps.RollingUpdateDeployment{
  2376  			MaxUnavailable: intstr.FromString("110%"),
  2377  		},
  2378  	}
  2379  	errorCases["must not be greater than 100%"] = invalidMaxUnavailableDeployment
  2380  
  2381  	// Rollback.Revision must be non-negative
  2382  	invalidRollbackRevisionDeployment := validDeployment()
  2383  	invalidRollbackRevisionDeployment.Spec.RollbackTo.Revision = -3
  2384  	errorCases["must be greater than or equal to 0"] = invalidRollbackRevisionDeployment
  2385  
  2386  	// ProgressDeadlineSeconds should be greater than MinReadySeconds
  2387  	invalidProgressDeadlineDeployment := validDeployment()
  2388  	seconds := int32(600)
  2389  	invalidProgressDeadlineDeployment.Spec.ProgressDeadlineSeconds = &seconds
  2390  	invalidProgressDeadlineDeployment.Spec.MinReadySeconds = seconds
  2391  	errorCases["must be greater than minReadySeconds"] = invalidProgressDeadlineDeployment
  2392  
  2393  	// Must not have ephemeral containers
  2394  	invalidEphemeralContainersDeployment := validDeployment()
  2395  	invalidEphemeralContainersDeployment.Spec.Template.Spec.EphemeralContainers = []api.EphemeralContainer{{
  2396  		EphemeralContainerCommon: api.EphemeralContainerCommon{
  2397  			Name:                     "ec",
  2398  			Image:                    "image",
  2399  			ImagePullPolicy:          "IfNotPresent",
  2400  			TerminationMessagePolicy: "File"},
  2401  	}}
  2402  	errorCases["ephemeral containers not allowed"] = invalidEphemeralContainersDeployment
  2403  
  2404  	for k, v := range errorCases {
  2405  		errs := ValidateDeployment(v, corevalidation.PodValidationOptions{})
  2406  		if len(errs) == 0 {
  2407  			t.Errorf("[%s] expected failure", k)
  2408  		} else if !strings.Contains(errs[0].Error(), k) {
  2409  			t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
  2410  		}
  2411  	}
  2412  }
  2413  
  2414  func TestValidateDeploymentStatus(t *testing.T) {
  2415  	collisionCount := int32(-3)
  2416  	tests := []struct {
  2417  		name string
  2418  
  2419  		replicas           int32
  2420  		updatedReplicas    int32
  2421  		readyReplicas      int32
  2422  		availableReplicas  int32
  2423  		observedGeneration int64
  2424  		collisionCount     *int32
  2425  
  2426  		expectedErr bool
  2427  	}{{
  2428  		name:               "valid status",
  2429  		replicas:           3,
  2430  		updatedReplicas:    3,
  2431  		readyReplicas:      2,
  2432  		availableReplicas:  1,
  2433  		observedGeneration: 2,
  2434  		expectedErr:        false,
  2435  	}, {
  2436  		name:               "invalid replicas",
  2437  		replicas:           -1,
  2438  		updatedReplicas:    2,
  2439  		readyReplicas:      2,
  2440  		availableReplicas:  1,
  2441  		observedGeneration: 2,
  2442  		expectedErr:        true,
  2443  	}, {
  2444  		name:               "invalid updatedReplicas",
  2445  		replicas:           2,
  2446  		updatedReplicas:    -1,
  2447  		readyReplicas:      2,
  2448  		availableReplicas:  1,
  2449  		observedGeneration: 2,
  2450  		expectedErr:        true,
  2451  	}, {
  2452  		name:               "invalid readyReplicas",
  2453  		replicas:           3,
  2454  		readyReplicas:      -1,
  2455  		availableReplicas:  1,
  2456  		observedGeneration: 2,
  2457  		expectedErr:        true,
  2458  	}, {
  2459  		name:               "invalid availableReplicas",
  2460  		replicas:           3,
  2461  		readyReplicas:      3,
  2462  		availableReplicas:  -1,
  2463  		observedGeneration: 2,
  2464  		expectedErr:        true,
  2465  	}, {
  2466  		name:               "invalid observedGeneration",
  2467  		replicas:           3,
  2468  		readyReplicas:      3,
  2469  		availableReplicas:  3,
  2470  		observedGeneration: -1,
  2471  		expectedErr:        true,
  2472  	}, {
  2473  		name:               "updatedReplicas greater than replicas",
  2474  		replicas:           3,
  2475  		updatedReplicas:    4,
  2476  		readyReplicas:      3,
  2477  		availableReplicas:  3,
  2478  		observedGeneration: 1,
  2479  		expectedErr:        true,
  2480  	}, {
  2481  		name:               "readyReplicas greater than replicas",
  2482  		replicas:           3,
  2483  		readyReplicas:      4,
  2484  		availableReplicas:  3,
  2485  		observedGeneration: 1,
  2486  		expectedErr:        true,
  2487  	}, {
  2488  		name:               "availableReplicas greater than replicas",
  2489  		replicas:           3,
  2490  		readyReplicas:      3,
  2491  		availableReplicas:  4,
  2492  		observedGeneration: 1,
  2493  		expectedErr:        true,
  2494  	}, {
  2495  		name:               "availableReplicas greater than readyReplicas",
  2496  		replicas:           3,
  2497  		readyReplicas:      2,
  2498  		availableReplicas:  3,
  2499  		observedGeneration: 1,
  2500  		expectedErr:        true,
  2501  	}, {
  2502  		name:               "invalid collisionCount",
  2503  		replicas:           3,
  2504  		observedGeneration: 1,
  2505  		collisionCount:     &collisionCount,
  2506  		expectedErr:        true,
  2507  	},
  2508  	}
  2509  
  2510  	for _, test := range tests {
  2511  		status := apps.DeploymentStatus{
  2512  			Replicas:           test.replicas,
  2513  			UpdatedReplicas:    test.updatedReplicas,
  2514  			ReadyReplicas:      test.readyReplicas,
  2515  			AvailableReplicas:  test.availableReplicas,
  2516  			ObservedGeneration: test.observedGeneration,
  2517  			CollisionCount:     test.collisionCount,
  2518  		}
  2519  
  2520  		errs := ValidateDeploymentStatus(&status, field.NewPath("status"))
  2521  		if hasErr := len(errs) > 0; hasErr != test.expectedErr {
  2522  			errString := dump.Pretty(errs)
  2523  			t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString)
  2524  		}
  2525  	}
  2526  }
  2527  
  2528  func TestValidateDeploymentStatusUpdate(t *testing.T) {
  2529  	collisionCount := int32(1)
  2530  	otherCollisionCount := int32(2)
  2531  	tests := []struct {
  2532  		name string
  2533  
  2534  		from, to apps.DeploymentStatus
  2535  
  2536  		expectedErr bool
  2537  	}{{
  2538  		name: "increase: valid update",
  2539  		from: apps.DeploymentStatus{
  2540  			CollisionCount: nil,
  2541  		},
  2542  		to: apps.DeploymentStatus{
  2543  			CollisionCount: &collisionCount,
  2544  		},
  2545  		expectedErr: false,
  2546  	}, {
  2547  		name: "stable: valid update",
  2548  		from: apps.DeploymentStatus{
  2549  			CollisionCount: &collisionCount,
  2550  		},
  2551  		to: apps.DeploymentStatus{
  2552  			CollisionCount: &collisionCount,
  2553  		},
  2554  		expectedErr: false,
  2555  	}, {
  2556  		name: "unset: invalid update",
  2557  		from: apps.DeploymentStatus{
  2558  			CollisionCount: &collisionCount,
  2559  		},
  2560  		to: apps.DeploymentStatus{
  2561  			CollisionCount: nil,
  2562  		},
  2563  		expectedErr: true,
  2564  	}, {
  2565  		name: "decrease: invalid update",
  2566  		from: apps.DeploymentStatus{
  2567  			CollisionCount: &otherCollisionCount,
  2568  		},
  2569  		to: apps.DeploymentStatus{
  2570  			CollisionCount: &collisionCount,
  2571  		},
  2572  		expectedErr: true,
  2573  	},
  2574  	}
  2575  
  2576  	for _, test := range tests {
  2577  		meta := metav1.ObjectMeta{Name: "foo", Namespace: metav1.NamespaceDefault, ResourceVersion: "1"}
  2578  		from := &apps.Deployment{
  2579  			ObjectMeta: meta,
  2580  			Status:     test.from,
  2581  		}
  2582  		to := &apps.Deployment{
  2583  			ObjectMeta: meta,
  2584  			Status:     test.to,
  2585  		}
  2586  
  2587  		errs := ValidateDeploymentStatusUpdate(to, from)
  2588  		if hasErr := len(errs) > 0; hasErr != test.expectedErr {
  2589  			errString := dump.Pretty(errs)
  2590  			t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString)
  2591  		}
  2592  	}
  2593  }
  2594  
  2595  func validDeploymentRollback() *apps.DeploymentRollback {
  2596  	return &apps.DeploymentRollback{
  2597  		Name: "abc",
  2598  		UpdatedAnnotations: map[string]string{
  2599  			"created-by": "abc",
  2600  		},
  2601  		RollbackTo: apps.RollbackConfig{
  2602  			Revision: 1,
  2603  		},
  2604  	}
  2605  }
  2606  
  2607  func TestValidateDeploymentUpdate(t *testing.T) {
  2608  	validLabels := map[string]string{"a": "b"}
  2609  	validPodTemplate := api.PodTemplate{
  2610  		Template: api.PodTemplateSpec{
  2611  			ObjectMeta: metav1.ObjectMeta{
  2612  				Labels: validLabels,
  2613  			},
  2614  			Spec: api.PodSpec{
  2615  				RestartPolicy: api.RestartPolicyAlways,
  2616  				DNSPolicy:     api.DNSClusterFirst,
  2617  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2618  			},
  2619  		},
  2620  	}
  2621  	readWriteVolumePodTemplate := api.PodTemplate{
  2622  		Template: api.PodTemplateSpec{
  2623  			ObjectMeta: metav1.ObjectMeta{
  2624  				Labels: validLabels,
  2625  			},
  2626  			Spec: api.PodSpec{
  2627  				RestartPolicy: api.RestartPolicyAlways,
  2628  				DNSPolicy:     api.DNSClusterFirst,
  2629  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2630  				Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  2631  			},
  2632  		},
  2633  	}
  2634  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  2635  	invalidPodTemplate := api.PodTemplate{
  2636  		Template: api.PodTemplateSpec{
  2637  			Spec: api.PodSpec{
  2638  				// no containers specified
  2639  				RestartPolicy: api.RestartPolicyAlways,
  2640  				DNSPolicy:     api.DNSClusterFirst,
  2641  			},
  2642  			ObjectMeta: metav1.ObjectMeta{
  2643  				Labels: invalidLabels,
  2644  			},
  2645  		},
  2646  	}
  2647  	type depUpdateTest struct {
  2648  		old                             apps.Deployment
  2649  		update                          apps.Deployment
  2650  		expectedErrNum                  int
  2651  		enableSkipReadOnlyValidationGCE bool
  2652  	}
  2653  	successCases := map[string]depUpdateTest{
  2654  		"positive replicas": {
  2655  			old: apps.Deployment{
  2656  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2657  				Spec: apps.DeploymentSpec{
  2658  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2659  					Template: validPodTemplate.Template,
  2660  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2661  				},
  2662  			},
  2663  			update: apps.Deployment{
  2664  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2665  				Spec: apps.DeploymentSpec{
  2666  					Replicas: 1,
  2667  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2668  					Template: readWriteVolumePodTemplate.Template,
  2669  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2670  				},
  2671  			},
  2672  		},
  2673  		"Read-write volume verification": {
  2674  			enableSkipReadOnlyValidationGCE: true,
  2675  			old: apps.Deployment{
  2676  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2677  				Spec: apps.DeploymentSpec{
  2678  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2679  					Template: validPodTemplate.Template,
  2680  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2681  				},
  2682  			},
  2683  			update: apps.Deployment{
  2684  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2685  				Spec: apps.DeploymentSpec{
  2686  					Replicas: 2,
  2687  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2688  					Template: readWriteVolumePodTemplate.Template,
  2689  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2690  				},
  2691  			},
  2692  		},
  2693  	}
  2694  	for testName, successCase := range successCases {
  2695  		t.Run(testName, func(t *testing.T) {
  2696  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, successCase.enableSkipReadOnlyValidationGCE)()
  2697  			// ResourceVersion is required for updates.
  2698  			successCase.old.ObjectMeta.ResourceVersion = "1"
  2699  			successCase.update.ObjectMeta.ResourceVersion = "2"
  2700  			// Check test setup
  2701  			if successCase.expectedErrNum > 0 {
  2702  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
  2703  			}
  2704  			if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
  2705  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  2706  			}
  2707  			// Run the tests
  2708  			if errs := ValidateDeploymentUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2709  				t.Errorf("%q expected no error, but got: %v", testName, errs)
  2710  			}
  2711  		})
  2712  		errorCases := map[string]depUpdateTest{
  2713  			"more than one read/write": {
  2714  				old: apps.Deployment{
  2715  					ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2716  					Spec: apps.DeploymentSpec{
  2717  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2718  						Template: validPodTemplate.Template,
  2719  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2720  					},
  2721  				},
  2722  				update: apps.Deployment{
  2723  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2724  					Spec: apps.DeploymentSpec{
  2725  						Replicas: 2,
  2726  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2727  						Template: readWriteVolumePodTemplate.Template,
  2728  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2729  					},
  2730  				},
  2731  				expectedErrNum: 2,
  2732  			},
  2733  			"invalid selector": {
  2734  				old: apps.Deployment{
  2735  					ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2736  					Spec: apps.DeploymentSpec{
  2737  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2738  						Template: validPodTemplate.Template,
  2739  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2740  					},
  2741  				},
  2742  				update: apps.Deployment{
  2743  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2744  					Spec: apps.DeploymentSpec{
  2745  						Replicas: 2,
  2746  						Selector: &metav1.LabelSelector{MatchLabels: invalidLabels},
  2747  						Template: validPodTemplate.Template,
  2748  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2749  					},
  2750  				},
  2751  				expectedErrNum: 3,
  2752  			},
  2753  			"invalid pod": {
  2754  				old: apps.Deployment{
  2755  					ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2756  					Spec: apps.DeploymentSpec{
  2757  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2758  						Template: validPodTemplate.Template,
  2759  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2760  					},
  2761  				},
  2762  				update: apps.Deployment{
  2763  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2764  					Spec: apps.DeploymentSpec{
  2765  						Replicas: 2,
  2766  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2767  						Template: invalidPodTemplate.Template,
  2768  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2769  					},
  2770  				},
  2771  				expectedErrNum: 4,
  2772  			},
  2773  			"negative replicas": {
  2774  				old: apps.Deployment{
  2775  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2776  					Spec: apps.DeploymentSpec{
  2777  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2778  						Template: validPodTemplate.Template,
  2779  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2780  					},
  2781  				},
  2782  				update: apps.Deployment{
  2783  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2784  					Spec: apps.DeploymentSpec{
  2785  						Replicas: -1,
  2786  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2787  						Template: readWriteVolumePodTemplate.Template,
  2788  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2789  					},
  2790  				},
  2791  				expectedErrNum: 1,
  2792  			},
  2793  		}
  2794  		for testName, errorCase := range errorCases {
  2795  			t.Run(testName, func(t *testing.T) {
  2796  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, errorCase.enableSkipReadOnlyValidationGCE)()
  2797  				// ResourceVersion is required for updates.
  2798  				errorCase.old.ObjectMeta.ResourceVersion = "1"
  2799  				errorCase.update.ObjectMeta.ResourceVersion = "2"
  2800  				// Check test setup
  2801  				if errorCase.expectedErrNum <= 0 {
  2802  					t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
  2803  				}
  2804  				if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
  2805  					t.Errorf("%q has incorrect test setup with no resource version set", testName)
  2806  				}
  2807  				// Run the tests
  2808  				if errs := ValidateDeploymentUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
  2809  					t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
  2810  				} else {
  2811  					t.Logf("(PASS) %q got errors %v", testName, errs)
  2812  				}
  2813  			})
  2814  		}
  2815  	}
  2816  }
  2817  
  2818  func TestValidateDeploymentRollback(t *testing.T) {
  2819  	noAnnotation := validDeploymentRollback()
  2820  	noAnnotation.UpdatedAnnotations = nil
  2821  	successCases := []*apps.DeploymentRollback{
  2822  		validDeploymentRollback(),
  2823  		noAnnotation,
  2824  	}
  2825  	for _, successCase := range successCases {
  2826  		if errs := ValidateDeploymentRollback(successCase); len(errs) != 0 {
  2827  			t.Errorf("expected success: %v", errs)
  2828  		}
  2829  	}
  2830  
  2831  	errorCases := map[string]*apps.DeploymentRollback{}
  2832  	invalidNoName := validDeploymentRollback()
  2833  	invalidNoName.Name = ""
  2834  	errorCases["name: Required value"] = invalidNoName
  2835  
  2836  	for k, v := range errorCases {
  2837  		errs := ValidateDeploymentRollback(v)
  2838  		if len(errs) == 0 {
  2839  			t.Errorf("[%s] expected failure", k)
  2840  		} else if !strings.Contains(errs[0].Error(), k) {
  2841  			t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
  2842  		}
  2843  	}
  2844  }
  2845  
  2846  func TestValidateReplicaSetStatus(t *testing.T) {
  2847  	tests := []struct {
  2848  		name string
  2849  
  2850  		replicas             int32
  2851  		fullyLabeledReplicas int32
  2852  		readyReplicas        int32
  2853  		availableReplicas    int32
  2854  		observedGeneration   int64
  2855  
  2856  		expectedErr bool
  2857  	}{{
  2858  		name:                 "valid status",
  2859  		replicas:             3,
  2860  		fullyLabeledReplicas: 3,
  2861  		readyReplicas:        2,
  2862  		availableReplicas:    1,
  2863  		observedGeneration:   2,
  2864  		expectedErr:          false,
  2865  	}, {
  2866  		name:                 "invalid replicas",
  2867  		replicas:             -1,
  2868  		fullyLabeledReplicas: 3,
  2869  		readyReplicas:        2,
  2870  		availableReplicas:    1,
  2871  		observedGeneration:   2,
  2872  		expectedErr:          true,
  2873  	}, {
  2874  		name:                 "invalid fullyLabeledReplicas",
  2875  		replicas:             3,
  2876  		fullyLabeledReplicas: -1,
  2877  		readyReplicas:        2,
  2878  		availableReplicas:    1,
  2879  		observedGeneration:   2,
  2880  		expectedErr:          true,
  2881  	}, {
  2882  		name:                 "invalid readyReplicas",
  2883  		replicas:             3,
  2884  		fullyLabeledReplicas: 3,
  2885  		readyReplicas:        -1,
  2886  		availableReplicas:    1,
  2887  		observedGeneration:   2,
  2888  		expectedErr:          true,
  2889  	}, {
  2890  		name:                 "invalid availableReplicas",
  2891  		replicas:             3,
  2892  		fullyLabeledReplicas: 3,
  2893  		readyReplicas:        3,
  2894  		availableReplicas:    -1,
  2895  		observedGeneration:   2,
  2896  		expectedErr:          true,
  2897  	}, {
  2898  		name:                 "invalid observedGeneration",
  2899  		replicas:             3,
  2900  		fullyLabeledReplicas: 3,
  2901  		readyReplicas:        3,
  2902  		availableReplicas:    3,
  2903  		observedGeneration:   -1,
  2904  		expectedErr:          true,
  2905  	}, {
  2906  		name:                 "fullyLabeledReplicas greater than replicas",
  2907  		replicas:             3,
  2908  		fullyLabeledReplicas: 4,
  2909  		readyReplicas:        3,
  2910  		availableReplicas:    3,
  2911  		observedGeneration:   1,
  2912  		expectedErr:          true,
  2913  	}, {
  2914  		name:                 "readyReplicas greater than replicas",
  2915  		replicas:             3,
  2916  		fullyLabeledReplicas: 3,
  2917  		readyReplicas:        4,
  2918  		availableReplicas:    3,
  2919  		observedGeneration:   1,
  2920  		expectedErr:          true,
  2921  	}, {
  2922  		name:                 "availableReplicas greater than replicas",
  2923  		replicas:             3,
  2924  		fullyLabeledReplicas: 3,
  2925  		readyReplicas:        3,
  2926  		availableReplicas:    4,
  2927  		observedGeneration:   1,
  2928  		expectedErr:          true,
  2929  	}, {
  2930  		name:                 "availableReplicas greater than readyReplicas",
  2931  		replicas:             3,
  2932  		fullyLabeledReplicas: 3,
  2933  		readyReplicas:        2,
  2934  		availableReplicas:    3,
  2935  		observedGeneration:   1,
  2936  		expectedErr:          true,
  2937  	},
  2938  	}
  2939  
  2940  	for _, test := range tests {
  2941  		status := apps.ReplicaSetStatus{
  2942  			Replicas:             test.replicas,
  2943  			FullyLabeledReplicas: test.fullyLabeledReplicas,
  2944  			ReadyReplicas:        test.readyReplicas,
  2945  			AvailableReplicas:    test.availableReplicas,
  2946  			ObservedGeneration:   test.observedGeneration,
  2947  		}
  2948  
  2949  		if hasErr := len(ValidateReplicaSetStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
  2950  			t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
  2951  		}
  2952  	}
  2953  }
  2954  
  2955  func TestValidateReplicaSetStatusUpdate(t *testing.T) {
  2956  	validLabels := map[string]string{"a": "b"}
  2957  	validPodTemplate := api.PodTemplate{
  2958  		Template: api.PodTemplateSpec{
  2959  			ObjectMeta: metav1.ObjectMeta{
  2960  				Labels: validLabels,
  2961  			},
  2962  			Spec: api.PodSpec{
  2963  				RestartPolicy: api.RestartPolicyAlways,
  2964  				DNSPolicy:     api.DNSClusterFirst,
  2965  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2966  			},
  2967  		},
  2968  	}
  2969  	type rcUpdateTest struct {
  2970  		old    apps.ReplicaSet
  2971  		update apps.ReplicaSet
  2972  	}
  2973  	successCases := []rcUpdateTest{{
  2974  		old: apps.ReplicaSet{
  2975  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2976  			Spec: apps.ReplicaSetSpec{
  2977  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2978  				Template: validPodTemplate.Template,
  2979  			},
  2980  			Status: apps.ReplicaSetStatus{
  2981  				Replicas: 2,
  2982  			},
  2983  		},
  2984  		update: apps.ReplicaSet{
  2985  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2986  			Spec: apps.ReplicaSetSpec{
  2987  				Replicas: 3,
  2988  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2989  				Template: validPodTemplate.Template,
  2990  			},
  2991  			Status: apps.ReplicaSetStatus{
  2992  				Replicas: 4,
  2993  			},
  2994  		},
  2995  	},
  2996  	}
  2997  	for _, successCase := range successCases {
  2998  		successCase.old.ObjectMeta.ResourceVersion = "1"
  2999  		successCase.update.ObjectMeta.ResourceVersion = "1"
  3000  		if errs := ValidateReplicaSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
  3001  			t.Errorf("expected success: %v", errs)
  3002  		}
  3003  	}
  3004  	errorCases := map[string]rcUpdateTest{
  3005  		"negative replicas": {
  3006  			old: apps.ReplicaSet{
  3007  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3008  				Spec: apps.ReplicaSetSpec{
  3009  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3010  					Template: validPodTemplate.Template,
  3011  				},
  3012  				Status: apps.ReplicaSetStatus{
  3013  					Replicas: 3,
  3014  				},
  3015  			},
  3016  			update: apps.ReplicaSet{
  3017  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3018  				Spec: apps.ReplicaSetSpec{
  3019  					Replicas: 2,
  3020  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3021  					Template: validPodTemplate.Template,
  3022  				},
  3023  				Status: apps.ReplicaSetStatus{
  3024  					Replicas: -3,
  3025  				},
  3026  			},
  3027  		},
  3028  	}
  3029  	for testName, errorCase := range errorCases {
  3030  		if errs := ValidateReplicaSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
  3031  			t.Errorf("expected failure: %s", testName)
  3032  		}
  3033  	}
  3034  
  3035  }
  3036  
  3037  func TestValidateReplicaSetUpdate(t *testing.T) {
  3038  	validLabels := map[string]string{"a": "b"}
  3039  	validPodTemplate := api.PodTemplate{
  3040  		Template: api.PodTemplateSpec{
  3041  			ObjectMeta: metav1.ObjectMeta{
  3042  				Labels: validLabels,
  3043  			},
  3044  			Spec: api.PodSpec{
  3045  				RestartPolicy: api.RestartPolicyAlways,
  3046  				DNSPolicy:     api.DNSClusterFirst,
  3047  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3048  			},
  3049  		},
  3050  	}
  3051  	readWriteVolumePodTemplate := api.PodTemplate{
  3052  		Template: api.PodTemplateSpec{
  3053  			ObjectMeta: metav1.ObjectMeta{
  3054  				Labels: validLabels,
  3055  			},
  3056  			Spec: api.PodSpec{
  3057  				RestartPolicy: api.RestartPolicyAlways,
  3058  				DNSPolicy:     api.DNSClusterFirst,
  3059  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3060  				Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  3061  			},
  3062  		},
  3063  	}
  3064  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  3065  	invalidPodTemplate := api.PodTemplate{
  3066  		Template: api.PodTemplateSpec{
  3067  			Spec: api.PodSpec{
  3068  				RestartPolicy: api.RestartPolicyAlways,
  3069  				DNSPolicy:     api.DNSClusterFirst,
  3070  			},
  3071  			ObjectMeta: metav1.ObjectMeta{
  3072  				Labels: invalidLabels,
  3073  			},
  3074  		},
  3075  	}
  3076  	type rcUpdateTest struct {
  3077  		old                             apps.ReplicaSet
  3078  		update                          apps.ReplicaSet
  3079  		expectedErrNum                  int
  3080  		enableSkipReadOnlyValidationGCE bool
  3081  	}
  3082  	successCases := map[string]rcUpdateTest{
  3083  		"positive replicas": {
  3084  			old: apps.ReplicaSet{
  3085  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3086  				Spec: apps.ReplicaSetSpec{
  3087  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3088  					Template: validPodTemplate.Template,
  3089  				},
  3090  			},
  3091  			update: apps.ReplicaSet{
  3092  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3093  				Spec: apps.ReplicaSetSpec{
  3094  					Replicas: 3,
  3095  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3096  					Template: validPodTemplate.Template,
  3097  				},
  3098  			},
  3099  		},
  3100  		"Read-write volume verification": {
  3101  			enableSkipReadOnlyValidationGCE: true,
  3102  			old: apps.ReplicaSet{
  3103  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3104  				Spec: apps.ReplicaSetSpec{
  3105  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3106  					Template: validPodTemplate.Template,
  3107  				},
  3108  			},
  3109  			update: apps.ReplicaSet{
  3110  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3111  				Spec: apps.ReplicaSetSpec{
  3112  					Replicas: 3,
  3113  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3114  					Template: readWriteVolumePodTemplate.Template,
  3115  				},
  3116  			},
  3117  		},
  3118  	}
  3119  	for testName, successCase := range successCases {
  3120  		t.Run(testName, func(t *testing.T) {
  3121  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, successCase.enableSkipReadOnlyValidationGCE)()
  3122  			// ResourceVersion is required for updates.
  3123  			successCase.old.ObjectMeta.ResourceVersion = "1"
  3124  			successCase.update.ObjectMeta.ResourceVersion = "2"
  3125  			// Check test setup
  3126  			if successCase.expectedErrNum > 0 {
  3127  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
  3128  			}
  3129  			if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
  3130  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  3131  			}
  3132  			// Run the tests
  3133  			if errs := ValidateReplicaSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  3134  				t.Errorf("%q expected no error, but got: %v", testName, errs)
  3135  			}
  3136  		})
  3137  	}
  3138  	errorCases := map[string]rcUpdateTest{
  3139  		"more than one read/write": {
  3140  			old: apps.ReplicaSet{
  3141  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3142  				Spec: apps.ReplicaSetSpec{
  3143  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3144  					Template: validPodTemplate.Template,
  3145  				},
  3146  			},
  3147  			update: apps.ReplicaSet{
  3148  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3149  				Spec: apps.ReplicaSetSpec{
  3150  					Replicas: 2,
  3151  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3152  					Template: readWriteVolumePodTemplate.Template,
  3153  				},
  3154  			},
  3155  			expectedErrNum: 2,
  3156  		},
  3157  		"invalid selector": {
  3158  			old: apps.ReplicaSet{
  3159  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3160  				Spec: apps.ReplicaSetSpec{
  3161  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3162  					Template: validPodTemplate.Template,
  3163  				},
  3164  			},
  3165  			update: apps.ReplicaSet{
  3166  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3167  				Spec: apps.ReplicaSetSpec{
  3168  					Replicas: 2,
  3169  					Selector: &metav1.LabelSelector{MatchLabels: invalidLabels},
  3170  					Template: validPodTemplate.Template,
  3171  				},
  3172  			},
  3173  			expectedErrNum: 3,
  3174  		},
  3175  		"invalid pod": {
  3176  			old: apps.ReplicaSet{
  3177  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3178  				Spec: apps.ReplicaSetSpec{
  3179  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3180  					Template: validPodTemplate.Template,
  3181  				},
  3182  			},
  3183  			update: apps.ReplicaSet{
  3184  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3185  				Spec: apps.ReplicaSetSpec{
  3186  					Replicas: 2,
  3187  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3188  					Template: invalidPodTemplate.Template,
  3189  				},
  3190  			},
  3191  			expectedErrNum: 4,
  3192  		},
  3193  		"negative replicas": {
  3194  			old: apps.ReplicaSet{
  3195  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3196  				Spec: apps.ReplicaSetSpec{
  3197  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3198  					Template: validPodTemplate.Template,
  3199  				},
  3200  			},
  3201  			update: apps.ReplicaSet{
  3202  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3203  				Spec: apps.ReplicaSetSpec{
  3204  					Replicas: -1,
  3205  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3206  					Template: validPodTemplate.Template,
  3207  				},
  3208  			},
  3209  			expectedErrNum: 1,
  3210  		},
  3211  	}
  3212  	for testName, errorCase := range errorCases {
  3213  		t.Run(testName, func(t *testing.T) {
  3214  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, errorCase.enableSkipReadOnlyValidationGCE)()
  3215  			// ResourceVersion is required for updates.
  3216  			errorCase.old.ObjectMeta.ResourceVersion = "1"
  3217  			errorCase.update.ObjectMeta.ResourceVersion = "2"
  3218  			// Check test setup
  3219  			if errorCase.expectedErrNum <= 0 {
  3220  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
  3221  			}
  3222  			if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
  3223  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  3224  			}
  3225  			// Run the tests
  3226  			if errs := ValidateReplicaSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
  3227  				t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
  3228  			} else {
  3229  				t.Logf("(PASS) %q got errors %v", testName, errs)
  3230  			}
  3231  		})
  3232  	}
  3233  }
  3234  
  3235  func TestValidateReplicaSet(t *testing.T) {
  3236  	validLabels := map[string]string{"a": "b"}
  3237  	validPodTemplate := api.PodTemplate{
  3238  		Template: api.PodTemplateSpec{
  3239  			ObjectMeta: metav1.ObjectMeta{
  3240  				Labels: validLabels,
  3241  			},
  3242  			Spec: api.PodSpec{
  3243  				RestartPolicy: api.RestartPolicyAlways,
  3244  				DNSPolicy:     api.DNSClusterFirst,
  3245  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3246  			},
  3247  		},
  3248  	}
  3249  	validHostNetPodTemplate := api.PodTemplate{
  3250  		Template: api.PodTemplateSpec{
  3251  			ObjectMeta: metav1.ObjectMeta{
  3252  				Labels: validLabels,
  3253  			},
  3254  			Spec: api.PodSpec{
  3255  				SecurityContext: &api.PodSecurityContext{
  3256  					HostNetwork: true,
  3257  				},
  3258  				RestartPolicy: api.RestartPolicyAlways,
  3259  				DNSPolicy:     api.DNSClusterFirst,
  3260  				Containers: []api.Container{{
  3261  					Name:                     "abc",
  3262  					Image:                    "image",
  3263  					ImagePullPolicy:          "IfNotPresent",
  3264  					TerminationMessagePolicy: api.TerminationMessageReadFile,
  3265  					Ports: []api.ContainerPort{{
  3266  						ContainerPort: 12345,
  3267  						Protocol:      api.ProtocolTCP,
  3268  					}},
  3269  				}},
  3270  			},
  3271  		},
  3272  	}
  3273  	readWriteVolumePodTemplate := api.PodTemplate{
  3274  		Template: api.PodTemplateSpec{
  3275  			ObjectMeta: metav1.ObjectMeta{
  3276  				Labels: validLabels,
  3277  			},
  3278  			Spec: api.PodSpec{
  3279  				Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  3280  				RestartPolicy: api.RestartPolicyAlways,
  3281  				DNSPolicy:     api.DNSClusterFirst,
  3282  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3283  			},
  3284  		},
  3285  	}
  3286  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  3287  	invalidPodTemplate := api.PodTemplate{
  3288  		Template: api.PodTemplateSpec{
  3289  			Spec: api.PodSpec{
  3290  				RestartPolicy: api.RestartPolicyAlways,
  3291  				DNSPolicy:     api.DNSClusterFirst,
  3292  			},
  3293  			ObjectMeta: metav1.ObjectMeta{
  3294  				Labels: invalidLabels,
  3295  			},
  3296  		},
  3297  	}
  3298  	successCases := []apps.ReplicaSet{{
  3299  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3300  		Spec: apps.ReplicaSetSpec{
  3301  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3302  			Template: validPodTemplate.Template,
  3303  		},
  3304  	}, {
  3305  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
  3306  		Spec: apps.ReplicaSetSpec{
  3307  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3308  			Template: validPodTemplate.Template,
  3309  		},
  3310  	}, {
  3311  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
  3312  		Spec: apps.ReplicaSetSpec{
  3313  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3314  			Template: validHostNetPodTemplate.Template,
  3315  		},
  3316  	}, {
  3317  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
  3318  		Spec: apps.ReplicaSetSpec{
  3319  			Replicas: 1,
  3320  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3321  			Template: readWriteVolumePodTemplate.Template,
  3322  		},
  3323  	},
  3324  	}
  3325  	for _, successCase := range successCases {
  3326  		if errs := ValidateReplicaSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  3327  			t.Errorf("expected success: %v", errs)
  3328  		}
  3329  	}
  3330  
  3331  	errorCases := map[string]apps.ReplicaSet{
  3332  		"zero-length ID": {
  3333  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3334  			Spec: apps.ReplicaSetSpec{
  3335  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3336  				Template: validPodTemplate.Template,
  3337  			},
  3338  		},
  3339  		"missing-namespace": {
  3340  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
  3341  			Spec: apps.ReplicaSetSpec{
  3342  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3343  				Template: validPodTemplate.Template,
  3344  			},
  3345  		},
  3346  		"empty selector": {
  3347  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3348  			Spec: apps.ReplicaSetSpec{
  3349  				Template: validPodTemplate.Template,
  3350  			},
  3351  		},
  3352  		"selector_doesnt_match": {
  3353  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3354  			Spec: apps.ReplicaSetSpec{
  3355  				Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  3356  				Template: validPodTemplate.Template,
  3357  			},
  3358  		},
  3359  		"invalid manifest": {
  3360  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3361  			Spec: apps.ReplicaSetSpec{
  3362  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3363  			},
  3364  		},
  3365  		"read-write persistent disk with > 1 pod": {
  3366  			ObjectMeta: metav1.ObjectMeta{Name: "abc"},
  3367  			Spec: apps.ReplicaSetSpec{
  3368  				Replicas: 2,
  3369  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3370  				Template: readWriteVolumePodTemplate.Template,
  3371  			},
  3372  		},
  3373  		"negative_replicas": {
  3374  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3375  			Spec: apps.ReplicaSetSpec{
  3376  				Replicas: -1,
  3377  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3378  			},
  3379  		},
  3380  		"invalid_label": {
  3381  			ObjectMeta: metav1.ObjectMeta{
  3382  				Name:      "abc-123",
  3383  				Namespace: metav1.NamespaceDefault,
  3384  				Labels: map[string]string{
  3385  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  3386  				},
  3387  			},
  3388  			Spec: apps.ReplicaSetSpec{
  3389  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3390  				Template: validPodTemplate.Template,
  3391  			},
  3392  		},
  3393  		"invalid_label 2": {
  3394  			ObjectMeta: metav1.ObjectMeta{
  3395  				Name:      "abc-123",
  3396  				Namespace: metav1.NamespaceDefault,
  3397  				Labels: map[string]string{
  3398  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  3399  				},
  3400  			},
  3401  			Spec: apps.ReplicaSetSpec{
  3402  				Template: invalidPodTemplate.Template,
  3403  			},
  3404  		},
  3405  		"invalid_annotation": {
  3406  			ObjectMeta: metav1.ObjectMeta{
  3407  				Name:      "abc-123",
  3408  				Namespace: metav1.NamespaceDefault,
  3409  				Annotations: map[string]string{
  3410  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  3411  				},
  3412  			},
  3413  			Spec: apps.ReplicaSetSpec{
  3414  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3415  				Template: validPodTemplate.Template,
  3416  			},
  3417  		},
  3418  		"invalid restart policy 1": {
  3419  			ObjectMeta: metav1.ObjectMeta{
  3420  				Name:      "abc-123",
  3421  				Namespace: metav1.NamespaceDefault,
  3422  			},
  3423  			Spec: apps.ReplicaSetSpec{
  3424  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3425  				Template: api.PodTemplateSpec{
  3426  					Spec: api.PodSpec{
  3427  						RestartPolicy: api.RestartPolicyOnFailure,
  3428  						DNSPolicy:     api.DNSClusterFirst,
  3429  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3430  					},
  3431  					ObjectMeta: metav1.ObjectMeta{
  3432  						Labels: validLabels,
  3433  					},
  3434  				},
  3435  			},
  3436  		},
  3437  		"invalid restart policy 2": {
  3438  			ObjectMeta: metav1.ObjectMeta{
  3439  				Name:      "abc-123",
  3440  				Namespace: metav1.NamespaceDefault,
  3441  			},
  3442  			Spec: apps.ReplicaSetSpec{
  3443  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3444  				Template: api.PodTemplateSpec{
  3445  					Spec: api.PodSpec{
  3446  						RestartPolicy: api.RestartPolicyNever,
  3447  						DNSPolicy:     api.DNSClusterFirst,
  3448  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3449  					},
  3450  					ObjectMeta: metav1.ObjectMeta{
  3451  						Labels: validLabels,
  3452  					},
  3453  				},
  3454  			},
  3455  		},
  3456  	}
  3457  	for k, v := range errorCases {
  3458  		errs := ValidateReplicaSet(&v, corevalidation.PodValidationOptions{})
  3459  		if len(errs) == 0 {
  3460  			t.Errorf("expected failure for %s", k)
  3461  		}
  3462  		for i := range errs {
  3463  			field := errs[i].Field
  3464  			if !strings.HasPrefix(field, "spec.template.") &&
  3465  				field != "metadata.name" &&
  3466  				field != "metadata.namespace" &&
  3467  				field != "spec.selector" &&
  3468  				field != "spec.template" &&
  3469  				field != "GCEPersistentDisk.ReadOnly" &&
  3470  				field != "spec.replicas" &&
  3471  				field != "spec.template.labels" &&
  3472  				field != "metadata.annotations" &&
  3473  				field != "metadata.labels" &&
  3474  				field != "status.replicas" {
  3475  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
  3476  			}
  3477  		}
  3478  	}
  3479  }
  3480  
  3481  func TestDaemonSetUpdateMaxSurge(t *testing.T) {
  3482  	testCases := map[string]struct {
  3483  		ds          *apps.RollingUpdateDaemonSet
  3484  		expectError bool
  3485  	}{
  3486  		"invalid: unset": {
  3487  			ds:          &apps.RollingUpdateDaemonSet{},
  3488  			expectError: true,
  3489  		},
  3490  		"invalid: zero percent": {
  3491  			ds: &apps.RollingUpdateDaemonSet{
  3492  				MaxUnavailable: intstr.FromString("0%"),
  3493  			},
  3494  			expectError: true,
  3495  		},
  3496  		"invalid: zero": {
  3497  			ds: &apps.RollingUpdateDaemonSet{
  3498  				MaxUnavailable: intstr.FromInt32(0),
  3499  			},
  3500  			expectError: true,
  3501  		},
  3502  		"valid: one": {
  3503  			ds: &apps.RollingUpdateDaemonSet{
  3504  				MaxUnavailable: intstr.FromInt32(1),
  3505  			},
  3506  		},
  3507  		"valid: one percent": {
  3508  			ds: &apps.RollingUpdateDaemonSet{
  3509  				MaxUnavailable: intstr.FromString("1%"),
  3510  			},
  3511  		},
  3512  		"valid: 100%": {
  3513  			ds: &apps.RollingUpdateDaemonSet{
  3514  				MaxUnavailable: intstr.FromString("100%"),
  3515  			},
  3516  		},
  3517  		"invalid: greater than 100%": {
  3518  			ds: &apps.RollingUpdateDaemonSet{
  3519  				MaxUnavailable: intstr.FromString("101%"),
  3520  			},
  3521  			expectError: true,
  3522  		},
  3523  
  3524  		"valid: surge and unavailable set": {
  3525  			ds: &apps.RollingUpdateDaemonSet{
  3526  				MaxUnavailable: intstr.FromString("1%"),
  3527  				MaxSurge:       intstr.FromString("1%"),
  3528  			},
  3529  			expectError: true,
  3530  		},
  3531  
  3532  		"invalid: surge enabled, unavailable zero percent": {
  3533  			ds: &apps.RollingUpdateDaemonSet{
  3534  				MaxUnavailable: intstr.FromString("0%"),
  3535  			},
  3536  			expectError: true,
  3537  		},
  3538  		"invalid: surge enabled, unavailable zero": {
  3539  			ds: &apps.RollingUpdateDaemonSet{
  3540  				MaxUnavailable: intstr.FromInt32(0),
  3541  			},
  3542  			expectError: true,
  3543  		},
  3544  		"valid: surge enabled, unavailable one": {
  3545  			ds: &apps.RollingUpdateDaemonSet{
  3546  				MaxUnavailable: intstr.FromInt32(1),
  3547  			},
  3548  		},
  3549  		"valid: surge enabled, unavailable one percent": {
  3550  			ds: &apps.RollingUpdateDaemonSet{
  3551  				MaxUnavailable: intstr.FromString("1%"),
  3552  			},
  3553  		},
  3554  		"valid: surge enabled, unavailable 100%": {
  3555  			ds: &apps.RollingUpdateDaemonSet{
  3556  				MaxUnavailable: intstr.FromString("100%"),
  3557  			},
  3558  		},
  3559  		"invalid: surge enabled, unavailable greater than 100%": {
  3560  			ds: &apps.RollingUpdateDaemonSet{
  3561  				MaxUnavailable: intstr.FromString("101%"),
  3562  			},
  3563  			expectError: true,
  3564  		},
  3565  
  3566  		"invalid: surge enabled, surge zero percent": {
  3567  			ds: &apps.RollingUpdateDaemonSet{
  3568  				MaxSurge: intstr.FromString("0%"),
  3569  			},
  3570  			expectError: true,
  3571  		},
  3572  		"invalid: surge enabled, surge zero": {
  3573  			ds: &apps.RollingUpdateDaemonSet{
  3574  				MaxSurge: intstr.FromInt32(0),
  3575  			},
  3576  			expectError: true,
  3577  		},
  3578  		"valid: surge enabled, surge one": {
  3579  			ds: &apps.RollingUpdateDaemonSet{
  3580  				MaxSurge: intstr.FromInt32(1),
  3581  			},
  3582  		},
  3583  		"valid: surge enabled, surge one percent": {
  3584  			ds: &apps.RollingUpdateDaemonSet{
  3585  				MaxSurge: intstr.FromString("1%"),
  3586  			},
  3587  		},
  3588  		"valid: surge enabled, surge 100%": {
  3589  			ds: &apps.RollingUpdateDaemonSet{
  3590  				MaxSurge: intstr.FromString("100%"),
  3591  			},
  3592  		},
  3593  		"invalid: surge enabled, surge greater than 100%": {
  3594  			ds: &apps.RollingUpdateDaemonSet{
  3595  				MaxSurge: intstr.FromString("101%"),
  3596  			},
  3597  			expectError: true,
  3598  		},
  3599  
  3600  		"invalid: surge enabled, surge and unavailable set": {
  3601  			ds: &apps.RollingUpdateDaemonSet{
  3602  				MaxUnavailable: intstr.FromString("1%"),
  3603  				MaxSurge:       intstr.FromString("1%"),
  3604  			},
  3605  			expectError: true,
  3606  		},
  3607  
  3608  		"invalid: surge enabled, surge and unavailable zero percent": {
  3609  			ds: &apps.RollingUpdateDaemonSet{
  3610  				MaxUnavailable: intstr.FromString("0%"),
  3611  				MaxSurge:       intstr.FromString("0%"),
  3612  			},
  3613  			expectError: true,
  3614  		},
  3615  		"invalid: surge enabled, surge and unavailable zero": {
  3616  			ds: &apps.RollingUpdateDaemonSet{
  3617  				MaxUnavailable: intstr.FromInt32(0),
  3618  				MaxSurge:       intstr.FromInt32(0),
  3619  			},
  3620  			expectError: true,
  3621  		},
  3622  		"invalid: surge enabled, surge and unavailable mixed zero": {
  3623  			ds: &apps.RollingUpdateDaemonSet{
  3624  				MaxUnavailable: intstr.FromInt32(0),
  3625  				MaxSurge:       intstr.FromString("0%"),
  3626  			},
  3627  			expectError: true,
  3628  		},
  3629  	}
  3630  	for tcName, tc := range testCases {
  3631  		t.Run(tcName, func(t *testing.T) {
  3632  			errs := ValidateRollingUpdateDaemonSet(tc.ds, field.NewPath("spec", "updateStrategy", "rollingUpdate"))
  3633  			if tc.expectError && len(errs) == 0 {
  3634  				t.Errorf("Unexpected success")
  3635  			}
  3636  			if !tc.expectError && len(errs) != 0 {
  3637  				t.Errorf("Unexpected error(s): %v", errs)
  3638  			}
  3639  		})
  3640  	}
  3641  }
  3642  

View as plain text