k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/apps/validation/validation_test.go (about)

     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  				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  	}
  1579  	successCases := map[string]dsUpdateTest{
  1580  		"no change": {
  1581  			old: apps.DaemonSet{
  1582  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1583  				Spec: apps.DaemonSetSpec{
  1584  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1585  					TemplateGeneration: 1,
  1586  					Template:           validPodTemplateAbc.Template,
  1587  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1588  						Type: apps.OnDeleteDaemonSetStrategyType,
  1589  					},
  1590  				},
  1591  			},
  1592  			update: apps.DaemonSet{
  1593  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1594  				Spec: apps.DaemonSetSpec{
  1595  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1596  					TemplateGeneration: 1,
  1597  					Template:           validPodTemplateAbc.Template,
  1598  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1599  						Type: apps.OnDeleteDaemonSetStrategyType,
  1600  					},
  1601  				},
  1602  			},
  1603  		},
  1604  		"change template and selector": {
  1605  			old: apps.DaemonSet{
  1606  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1607  				Spec: apps.DaemonSetSpec{
  1608  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1609  					TemplateGeneration: 2,
  1610  					Template:           validPodTemplateAbc.Template,
  1611  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1612  						Type: apps.OnDeleteDaemonSetStrategyType,
  1613  					},
  1614  				},
  1615  			},
  1616  			update: apps.DaemonSet{
  1617  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1618  				Spec: apps.DaemonSetSpec{
  1619  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector2},
  1620  					TemplateGeneration: 3,
  1621  					Template:           validPodTemplateAbc2.Template,
  1622  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1623  						Type: apps.OnDeleteDaemonSetStrategyType,
  1624  					},
  1625  				},
  1626  			},
  1627  		},
  1628  		"change template": {
  1629  			old: apps.DaemonSet{
  1630  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1631  				Spec: apps.DaemonSetSpec{
  1632  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1633  					TemplateGeneration: 3,
  1634  					Template:           validPodTemplateAbc.Template,
  1635  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1636  						Type: apps.OnDeleteDaemonSetStrategyType,
  1637  					},
  1638  				},
  1639  			},
  1640  			update: apps.DaemonSet{
  1641  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1642  				Spec: apps.DaemonSetSpec{
  1643  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1644  					TemplateGeneration: 4,
  1645  					Template:           validPodTemplateNodeSelector.Template,
  1646  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1647  						Type: apps.OnDeleteDaemonSetStrategyType,
  1648  					},
  1649  				},
  1650  			},
  1651  		},
  1652  		"change container image name": {
  1653  			old: apps.DaemonSet{
  1654  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1655  				Spec: apps.DaemonSetSpec{
  1656  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1657  					TemplateGeneration: 1,
  1658  					Template:           validPodTemplateAbc.Template,
  1659  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1660  						Type: apps.OnDeleteDaemonSetStrategyType,
  1661  					},
  1662  				},
  1663  			},
  1664  			update: apps.DaemonSet{
  1665  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1666  				Spec: apps.DaemonSetSpec{
  1667  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector2},
  1668  					TemplateGeneration: 2,
  1669  					Template:           validPodTemplateDef.Template,
  1670  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1671  						Type: apps.OnDeleteDaemonSetStrategyType,
  1672  					},
  1673  				},
  1674  			},
  1675  		},
  1676  		"change update strategy": {
  1677  			old: apps.DaemonSet{
  1678  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1679  				Spec: apps.DaemonSetSpec{
  1680  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1681  					TemplateGeneration: 4,
  1682  					Template:           validPodTemplateAbc.Template,
  1683  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1684  						Type: apps.OnDeleteDaemonSetStrategyType,
  1685  					},
  1686  				},
  1687  			},
  1688  			update: apps.DaemonSet{
  1689  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1690  				Spec: apps.DaemonSetSpec{
  1691  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1692  					TemplateGeneration: 4,
  1693  					Template:           validPodTemplateAbc.Template,
  1694  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1695  						Type: apps.RollingUpdateDaemonSetStrategyType,
  1696  						RollingUpdate: &apps.RollingUpdateDaemonSet{
  1697  							MaxUnavailable: intstr.FromInt32(1),
  1698  						},
  1699  					},
  1700  				},
  1701  			},
  1702  		},
  1703  		"unchanged templateGeneration upon semantically equal template update": {
  1704  			old: apps.DaemonSet{
  1705  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1706  				Spec: apps.DaemonSetSpec{
  1707  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1708  					TemplateGeneration: 4,
  1709  					Template:           validPodTemplateAbc.Template,
  1710  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1711  						Type: apps.OnDeleteDaemonSetStrategyType,
  1712  					},
  1713  				},
  1714  			},
  1715  			update: apps.DaemonSet{
  1716  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1717  				Spec: apps.DaemonSetSpec{
  1718  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1719  					TemplateGeneration: 4,
  1720  					Template:           validPodTemplateAbcSemanticallyEqual.Template,
  1721  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1722  						Type: apps.RollingUpdateDaemonSetStrategyType,
  1723  						RollingUpdate: &apps.RollingUpdateDaemonSet{
  1724  							MaxUnavailable: intstr.FromInt32(1),
  1725  						},
  1726  					},
  1727  				},
  1728  			},
  1729  		},
  1730  		"Read-write volume verification": {
  1731  			old: apps.DaemonSet{
  1732  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1733  				Spec: apps.DaemonSetSpec{
  1734  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1735  					TemplateGeneration: 1,
  1736  					Template:           validPodTemplateAbc.Template,
  1737  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1738  						Type: apps.OnDeleteDaemonSetStrategyType,
  1739  					},
  1740  				},
  1741  			},
  1742  			update: apps.DaemonSet{
  1743  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1744  				Spec: apps.DaemonSetSpec{
  1745  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1746  					TemplateGeneration: 2,
  1747  					Template:           readWriteVolumePodTemplate.Template,
  1748  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1749  						Type: apps.OnDeleteDaemonSetStrategyType,
  1750  					},
  1751  				},
  1752  			},
  1753  		},
  1754  	}
  1755  	for testName, successCase := range successCases {
  1756  		t.Run(testName, func(t *testing.T) {
  1757  			// ResourceVersion is required for updates.
  1758  			successCase.old.ObjectMeta.ResourceVersion = "1"
  1759  			successCase.update.ObjectMeta.ResourceVersion = "2"
  1760  			// Check test setup
  1761  			if successCase.expectedErrNum > 0 {
  1762  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
  1763  			}
  1764  			if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
  1765  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  1766  			}
  1767  			if errs := ValidateDaemonSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  1768  				t.Errorf("%q expected no error, but got: %v", testName, errs)
  1769  			}
  1770  		})
  1771  	}
  1772  	errorCases := map[string]dsUpdateTest{
  1773  		"change daemon name": {
  1774  			old: apps.DaemonSet{
  1775  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  1776  				Spec: apps.DaemonSetSpec{
  1777  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1778  					TemplateGeneration: 1,
  1779  					Template:           validPodTemplateAbc.Template,
  1780  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1781  						Type: apps.OnDeleteDaemonSetStrategyType,
  1782  					},
  1783  				},
  1784  			},
  1785  			update: apps.DaemonSet{
  1786  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1787  				Spec: apps.DaemonSetSpec{
  1788  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1789  					TemplateGeneration: 1,
  1790  					Template:           validPodTemplateAbc.Template,
  1791  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1792  						Type: apps.OnDeleteDaemonSetStrategyType,
  1793  					},
  1794  				},
  1795  			},
  1796  			expectedErrNum: 1,
  1797  		},
  1798  		"invalid selector": {
  1799  			old: apps.DaemonSet{
  1800  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1801  				Spec: apps.DaemonSetSpec{
  1802  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1803  					TemplateGeneration: 1,
  1804  					Template:           validPodTemplateAbc.Template,
  1805  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1806  						Type: apps.OnDeleteDaemonSetStrategyType,
  1807  					},
  1808  				},
  1809  			},
  1810  			update: apps.DaemonSet{
  1811  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1812  				Spec: apps.DaemonSetSpec{
  1813  					Selector:           &metav1.LabelSelector{MatchLabels: invalidSelector},
  1814  					TemplateGeneration: 1,
  1815  					Template:           validPodTemplateAbc.Template,
  1816  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1817  						Type: apps.OnDeleteDaemonSetStrategyType,
  1818  					},
  1819  				},
  1820  			},
  1821  			expectedErrNum: 1,
  1822  		},
  1823  		"invalid pod": {
  1824  			old: apps.DaemonSet{
  1825  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1826  				Spec: apps.DaemonSetSpec{
  1827  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1828  					TemplateGeneration: 1,
  1829  					Template:           validPodTemplateAbc.Template,
  1830  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1831  						Type: apps.OnDeleteDaemonSetStrategyType,
  1832  					},
  1833  				},
  1834  			},
  1835  			update: apps.DaemonSet{
  1836  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1837  				Spec: apps.DaemonSetSpec{
  1838  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1839  					TemplateGeneration: 2,
  1840  					Template:           invalidPodTemplate.Template,
  1841  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1842  						Type: apps.OnDeleteDaemonSetStrategyType,
  1843  					},
  1844  				},
  1845  			},
  1846  			expectedErrNum: 1,
  1847  		},
  1848  		"invalid update strategy": {
  1849  			old: apps.DaemonSet{
  1850  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1851  				Spec: apps.DaemonSetSpec{
  1852  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1853  					TemplateGeneration: 1,
  1854  					Template:           validPodTemplateAbc.Template,
  1855  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1856  						Type: apps.OnDeleteDaemonSetStrategyType,
  1857  					},
  1858  				},
  1859  			},
  1860  			update: apps.DaemonSet{
  1861  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1862  				Spec: apps.DaemonSetSpec{
  1863  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1864  					TemplateGeneration: 1,
  1865  					Template:           validPodTemplateAbc.Template,
  1866  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1867  						Type: "Random",
  1868  					},
  1869  				},
  1870  			},
  1871  			expectedErrNum: 1,
  1872  		},
  1873  		"negative templateGeneration": {
  1874  			old: apps.DaemonSet{
  1875  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1876  				Spec: apps.DaemonSetSpec{
  1877  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1878  					TemplateGeneration: -1,
  1879  					Template:           validPodTemplateAbc.Template,
  1880  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1881  						Type: apps.OnDeleteDaemonSetStrategyType,
  1882  					},
  1883  				},
  1884  			},
  1885  			update: apps.DaemonSet{
  1886  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1887  				Spec: apps.DaemonSetSpec{
  1888  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1889  					TemplateGeneration: -1,
  1890  					Template:           validPodTemplateAbc.Template,
  1891  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1892  						Type: apps.OnDeleteDaemonSetStrategyType,
  1893  					},
  1894  				},
  1895  			},
  1896  			expectedErrNum: 1,
  1897  		},
  1898  		"decreased templateGeneration": {
  1899  			old: apps.DaemonSet{
  1900  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1901  				Spec: apps.DaemonSetSpec{
  1902  					TemplateGeneration: 2,
  1903  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1904  					Template:           validPodTemplateAbc.Template,
  1905  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1906  						Type: apps.OnDeleteDaemonSetStrategyType,
  1907  					},
  1908  				},
  1909  			},
  1910  			update: apps.DaemonSet{
  1911  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1912  				Spec: apps.DaemonSetSpec{
  1913  					TemplateGeneration: 1,
  1914  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1915  					Template:           validPodTemplateAbc.Template,
  1916  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1917  						Type: apps.OnDeleteDaemonSetStrategyType,
  1918  					},
  1919  				},
  1920  			},
  1921  			expectedErrNum: 1,
  1922  		},
  1923  		"unchanged templateGeneration upon template update": {
  1924  			old: apps.DaemonSet{
  1925  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1926  				Spec: apps.DaemonSetSpec{
  1927  					TemplateGeneration: 2,
  1928  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector},
  1929  					Template:           validPodTemplateAbc.Template,
  1930  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1931  						Type: apps.OnDeleteDaemonSetStrategyType,
  1932  					},
  1933  				},
  1934  			},
  1935  			update: apps.DaemonSet{
  1936  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1937  				Spec: apps.DaemonSetSpec{
  1938  					TemplateGeneration: 2,
  1939  					Selector:           &metav1.LabelSelector{MatchLabels: validSelector2},
  1940  					Template:           validPodTemplateAbc2.Template,
  1941  					UpdateStrategy: apps.DaemonSetUpdateStrategy{
  1942  						Type: apps.OnDeleteDaemonSetStrategyType,
  1943  					},
  1944  				},
  1945  			},
  1946  			expectedErrNum: 1,
  1947  		},
  1948  	}
  1949  	for testName, errorCase := range errorCases {
  1950  		t.Run(testName, func(t *testing.T) {
  1951  			// ResourceVersion is required for updates.
  1952  			errorCase.old.ObjectMeta.ResourceVersion = "1"
  1953  			errorCase.update.ObjectMeta.ResourceVersion = "2"
  1954  			// Check test setup
  1955  			if errorCase.expectedErrNum <= 0 {
  1956  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
  1957  			}
  1958  			if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
  1959  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  1960  			}
  1961  			// Run the tests
  1962  			if errs := ValidateDaemonSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
  1963  				t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
  1964  			} else {
  1965  				t.Logf("(PASS) %q got errors %v", testName, errs)
  1966  			}
  1967  		})
  1968  	}
  1969  }
  1970  
  1971  func TestValidateDaemonSet(t *testing.T) {
  1972  	validSelector := map[string]string{"a": "b"}
  1973  	validPodTemplate := api.PodTemplate{
  1974  		Template: api.PodTemplateSpec{
  1975  			ObjectMeta: metav1.ObjectMeta{
  1976  				Labels: validSelector,
  1977  			},
  1978  			Spec: api.PodSpec{
  1979  				RestartPolicy: api.RestartPolicyAlways,
  1980  				DNSPolicy:     api.DNSClusterFirst,
  1981  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1982  			},
  1983  		},
  1984  	}
  1985  	validHostNetPodTemplate := api.PodTemplate{
  1986  		Template: api.PodTemplateSpec{
  1987  			ObjectMeta: metav1.ObjectMeta{
  1988  				Labels: validSelector,
  1989  			},
  1990  			Spec: api.PodSpec{
  1991  				SecurityContext: &api.PodSecurityContext{
  1992  					HostNetwork: true,
  1993  				},
  1994  				RestartPolicy: api.RestartPolicyAlways,
  1995  				DNSPolicy:     api.DNSClusterFirst,
  1996  				Containers: []api.Container{{
  1997  					Name:                     "abc",
  1998  					Image:                    "image",
  1999  					ImagePullPolicy:          "IfNotPresent",
  2000  					TerminationMessagePolicy: api.TerminationMessageReadFile,
  2001  					Ports: []api.ContainerPort{{
  2002  						ContainerPort: 12345,
  2003  						Protocol:      api.ProtocolTCP,
  2004  					}},
  2005  				}},
  2006  			},
  2007  		},
  2008  	}
  2009  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  2010  	invalidPodTemplate := api.PodTemplate{
  2011  		Template: api.PodTemplateSpec{
  2012  			Spec: api.PodSpec{
  2013  				RestartPolicy: api.RestartPolicyAlways,
  2014  				DNSPolicy:     api.DNSClusterFirst,
  2015  			},
  2016  			ObjectMeta: metav1.ObjectMeta{
  2017  				Labels: invalidSelector,
  2018  			},
  2019  		},
  2020  	}
  2021  	successCases := []apps.DaemonSet{{
  2022  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2023  		Spec: apps.DaemonSetSpec{
  2024  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2025  			Template: validPodTemplate.Template,
  2026  			UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2027  				Type: apps.OnDeleteDaemonSetStrategyType,
  2028  			},
  2029  		},
  2030  	}, {
  2031  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
  2032  		Spec: apps.DaemonSetSpec{
  2033  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2034  			Template: validPodTemplate.Template,
  2035  			UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2036  				Type: apps.OnDeleteDaemonSetStrategyType,
  2037  			},
  2038  		},
  2039  	}, {
  2040  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
  2041  		Spec: apps.DaemonSetSpec{
  2042  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2043  			Template: validHostNetPodTemplate.Template,
  2044  			UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2045  				Type: apps.OnDeleteDaemonSetStrategyType,
  2046  			},
  2047  		},
  2048  	},
  2049  	}
  2050  	for _, successCase := range successCases {
  2051  		if errs := ValidateDaemonSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2052  			t.Errorf("expected success: %v", errs)
  2053  		}
  2054  	}
  2055  
  2056  	errorCases := map[string]apps.DaemonSet{
  2057  		"zero-length ID": {
  2058  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2059  			Spec: apps.DaemonSetSpec{
  2060  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2061  				Template: validPodTemplate.Template,
  2062  			},
  2063  		},
  2064  		"missing-namespace": {
  2065  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
  2066  			Spec: apps.DaemonSetSpec{
  2067  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2068  				Template: validPodTemplate.Template,
  2069  			},
  2070  		},
  2071  		"nil selector": {
  2072  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2073  			Spec: apps.DaemonSetSpec{
  2074  				Template: validPodTemplate.Template,
  2075  			},
  2076  		},
  2077  		"empty selector": {
  2078  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2079  			Spec: apps.DaemonSetSpec{
  2080  				Selector: &metav1.LabelSelector{},
  2081  				Template: validPodTemplate.Template,
  2082  			},
  2083  		},
  2084  		"selector_doesnt_match": {
  2085  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2086  			Spec: apps.DaemonSetSpec{
  2087  				Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  2088  				Template: validPodTemplate.Template,
  2089  			},
  2090  		},
  2091  		"invalid template": {
  2092  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2093  			Spec: apps.DaemonSetSpec{
  2094  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2095  			},
  2096  		},
  2097  		"invalid_label": {
  2098  			ObjectMeta: metav1.ObjectMeta{
  2099  				Name:      "abc-123",
  2100  				Namespace: metav1.NamespaceDefault,
  2101  				Labels: map[string]string{
  2102  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  2103  				},
  2104  			},
  2105  			Spec: apps.DaemonSetSpec{
  2106  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2107  				Template: validPodTemplate.Template,
  2108  			},
  2109  		},
  2110  		"invalid_label 2": {
  2111  			ObjectMeta: metav1.ObjectMeta{
  2112  				Name:      "abc-123",
  2113  				Namespace: metav1.NamespaceDefault,
  2114  				Labels: map[string]string{
  2115  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  2116  				},
  2117  			},
  2118  			Spec: apps.DaemonSetSpec{
  2119  				Template: invalidPodTemplate.Template,
  2120  			},
  2121  		},
  2122  		"invalid_annotation": {
  2123  			ObjectMeta: metav1.ObjectMeta{
  2124  				Name:      "abc-123",
  2125  				Namespace: metav1.NamespaceDefault,
  2126  				Annotations: map[string]string{
  2127  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  2128  				},
  2129  			},
  2130  			Spec: apps.DaemonSetSpec{
  2131  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2132  				Template: validPodTemplate.Template,
  2133  			},
  2134  		},
  2135  		"invalid restart policy 1": {
  2136  			ObjectMeta: metav1.ObjectMeta{
  2137  				Name:      "abc-123",
  2138  				Namespace: metav1.NamespaceDefault,
  2139  			},
  2140  			Spec: apps.DaemonSetSpec{
  2141  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2142  				Template: api.PodTemplateSpec{
  2143  					Spec: api.PodSpec{
  2144  						RestartPolicy: api.RestartPolicyOnFailure,
  2145  						DNSPolicy:     api.DNSClusterFirst,
  2146  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2147  					},
  2148  					ObjectMeta: metav1.ObjectMeta{
  2149  						Labels: validSelector,
  2150  					},
  2151  				},
  2152  			},
  2153  		},
  2154  		"invalid restart policy 2": {
  2155  			ObjectMeta: metav1.ObjectMeta{
  2156  				Name:      "abc-123",
  2157  				Namespace: metav1.NamespaceDefault,
  2158  			},
  2159  			Spec: apps.DaemonSetSpec{
  2160  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2161  				Template: api.PodTemplateSpec{
  2162  					Spec: api.PodSpec{
  2163  						RestartPolicy: api.RestartPolicyNever,
  2164  						DNSPolicy:     api.DNSClusterFirst,
  2165  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2166  					},
  2167  					ObjectMeta: metav1.ObjectMeta{
  2168  						Labels: validSelector,
  2169  					},
  2170  				},
  2171  			},
  2172  		},
  2173  		"template may not contain ephemeral containers": {
  2174  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2175  			Spec: apps.DaemonSetSpec{
  2176  				Selector: &metav1.LabelSelector{MatchLabels: validSelector},
  2177  				Template: api.PodTemplateSpec{
  2178  					ObjectMeta: metav1.ObjectMeta{
  2179  						Labels: validSelector,
  2180  					},
  2181  					Spec: api.PodSpec{
  2182  						RestartPolicy:       api.RestartPolicyAlways,
  2183  						DNSPolicy:           api.DNSClusterFirst,
  2184  						Containers:          []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2185  						EphemeralContainers: []api.EphemeralContainer{{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
  2186  					},
  2187  				},
  2188  				UpdateStrategy: apps.DaemonSetUpdateStrategy{
  2189  					Type: apps.OnDeleteDaemonSetStrategyType,
  2190  				},
  2191  			},
  2192  		},
  2193  	}
  2194  	for k, v := range errorCases {
  2195  		errs := ValidateDaemonSet(&v, corevalidation.PodValidationOptions{})
  2196  		if len(errs) == 0 {
  2197  			t.Errorf("expected failure for %s", k)
  2198  		}
  2199  		for i := range errs {
  2200  			field := errs[i].Field
  2201  			if !strings.HasPrefix(field, "spec.template.") &&
  2202  				!strings.HasPrefix(field, "spec.updateStrategy") &&
  2203  				field != "metadata.name" &&
  2204  				field != "metadata.namespace" &&
  2205  				field != "spec.selector" &&
  2206  				field != "spec.template" &&
  2207  				field != "GCEPersistentDisk.ReadOnly" &&
  2208  				field != "spec.template.labels" &&
  2209  				field != "metadata.annotations" &&
  2210  				field != "metadata.labels" {
  2211  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
  2212  			}
  2213  		}
  2214  	}
  2215  }
  2216  
  2217  func validDeployment(tweaks ...func(d *apps.Deployment)) *apps.Deployment {
  2218  	d := &apps.Deployment{
  2219  		ObjectMeta: metav1.ObjectMeta{
  2220  			Name:      "abc",
  2221  			Namespace: metav1.NamespaceDefault,
  2222  		},
  2223  		Spec: apps.DeploymentSpec{
  2224  			Selector: &metav1.LabelSelector{
  2225  				MatchLabels: map[string]string{
  2226  					"name": "abc",
  2227  				},
  2228  			},
  2229  			Strategy: apps.DeploymentStrategy{
  2230  				Type: apps.RollingUpdateDeploymentStrategyType,
  2231  				RollingUpdate: &apps.RollingUpdateDeployment{
  2232  					MaxSurge:       intstr.FromInt32(1),
  2233  					MaxUnavailable: intstr.FromInt32(1),
  2234  				},
  2235  			},
  2236  			Template: api.PodTemplateSpec{
  2237  				ObjectMeta: metav1.ObjectMeta{
  2238  					Name:      "abc",
  2239  					Namespace: metav1.NamespaceDefault,
  2240  					Labels: map[string]string{
  2241  						"name": "abc",
  2242  					},
  2243  				},
  2244  				Spec: api.PodSpec{
  2245  					RestartPolicy: api.RestartPolicyAlways,
  2246  					DNSPolicy:     api.DNSDefault,
  2247  					Containers: []api.Container{{
  2248  						Name:                     "nginx",
  2249  						Image:                    "image",
  2250  						ImagePullPolicy:          api.PullNever,
  2251  						TerminationMessagePolicy: api.TerminationMessageReadFile,
  2252  					}},
  2253  				},
  2254  			},
  2255  			RollbackTo: &apps.RollbackConfig{
  2256  				Revision: 1,
  2257  			},
  2258  		},
  2259  	}
  2260  
  2261  	for _, tweak := range tweaks {
  2262  		tweak(d)
  2263  	}
  2264  
  2265  	return d
  2266  }
  2267  
  2268  func TestValidateDeployment(t *testing.T) {
  2269  	successCases := []*apps.Deployment{
  2270  		validDeployment(),
  2271  		validDeployment(func(d *apps.Deployment) {
  2272  			d.Spec.Template.Spec.SecurityContext = &api.PodSecurityContext{
  2273  				HostNetwork: true,
  2274  			}
  2275  			d.Spec.Template.Spec.Containers[0].Ports = []api.ContainerPort{{
  2276  				ContainerPort: 12345,
  2277  				Protocol:      api.ProtocolTCP,
  2278  			}}
  2279  		}),
  2280  	}
  2281  	for _, successCase := range successCases {
  2282  		if errs := ValidateDeployment(successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2283  			t.Errorf("expected success: %v", errs)
  2284  		}
  2285  	}
  2286  
  2287  	errorCases := map[string]*apps.Deployment{}
  2288  	errorCases["metadata.name: Required value"] = &apps.Deployment{
  2289  		ObjectMeta: metav1.ObjectMeta{
  2290  			Namespace: metav1.NamespaceDefault,
  2291  		},
  2292  	}
  2293  	// selector should match the labels in pod template.
  2294  	invalidSelectorDeployment := validDeployment()
  2295  	invalidSelectorDeployment.Spec.Selector = &metav1.LabelSelector{
  2296  		MatchLabels: map[string]string{
  2297  			"name": "def",
  2298  		},
  2299  	}
  2300  	errorCases["`selector` does not match template `labels`"] = invalidSelectorDeployment
  2301  
  2302  	// RestartPolicy should be always.
  2303  	invalidRestartPolicyDeployment := validDeployment()
  2304  	invalidRestartPolicyDeployment.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever
  2305  	errorCases["Unsupported value: \"Never\""] = invalidRestartPolicyDeployment
  2306  
  2307  	// must have valid strategy type
  2308  	invalidStrategyDeployment := validDeployment()
  2309  	invalidStrategyDeployment.Spec.Strategy.Type = apps.DeploymentStrategyType("randomType")
  2310  	errorCases[`supported values: "Recreate", "RollingUpdate"`] = invalidStrategyDeployment
  2311  
  2312  	// rollingUpdate should be nil for recreate.
  2313  	invalidRecreateDeployment := validDeployment()
  2314  	invalidRecreateDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2315  		Type:          apps.RecreateDeploymentStrategyType,
  2316  		RollingUpdate: &apps.RollingUpdateDeployment{},
  2317  	}
  2318  	errorCases["may not be specified when strategy `type` is 'Recreate'"] = invalidRecreateDeployment
  2319  
  2320  	// MaxSurge should be in the form of 20%.
  2321  	invalidMaxSurgeDeployment := validDeployment()
  2322  	invalidMaxSurgeDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2323  		Type: apps.RollingUpdateDeploymentStrategyType,
  2324  		RollingUpdate: &apps.RollingUpdateDeployment{
  2325  			MaxSurge: intstr.FromString("20Percent"),
  2326  		},
  2327  	}
  2328  	errorCases["a valid percent string must be"] = invalidMaxSurgeDeployment
  2329  
  2330  	// MaxSurge and MaxUnavailable cannot both be zero.
  2331  	invalidRollingUpdateDeployment := validDeployment()
  2332  	invalidRollingUpdateDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2333  		Type: apps.RollingUpdateDeploymentStrategyType,
  2334  		RollingUpdate: &apps.RollingUpdateDeployment{
  2335  			MaxSurge:       intstr.FromString("0%"),
  2336  			MaxUnavailable: intstr.FromInt32(0),
  2337  		},
  2338  	}
  2339  	errorCases["may not be 0 when `maxSurge` is 0"] = invalidRollingUpdateDeployment
  2340  
  2341  	// MaxUnavailable should not be more than 100%.
  2342  	invalidMaxUnavailableDeployment := validDeployment()
  2343  	invalidMaxUnavailableDeployment.Spec.Strategy = apps.DeploymentStrategy{
  2344  		Type: apps.RollingUpdateDeploymentStrategyType,
  2345  		RollingUpdate: &apps.RollingUpdateDeployment{
  2346  			MaxUnavailable: intstr.FromString("110%"),
  2347  		},
  2348  	}
  2349  	errorCases["must not be greater than 100%"] = invalidMaxUnavailableDeployment
  2350  
  2351  	// Rollback.Revision must be non-negative
  2352  	invalidRollbackRevisionDeployment := validDeployment()
  2353  	invalidRollbackRevisionDeployment.Spec.RollbackTo.Revision = -3
  2354  	errorCases["must be greater than or equal to 0"] = invalidRollbackRevisionDeployment
  2355  
  2356  	// ProgressDeadlineSeconds should be greater than MinReadySeconds
  2357  	invalidProgressDeadlineDeployment := validDeployment()
  2358  	seconds := int32(600)
  2359  	invalidProgressDeadlineDeployment.Spec.ProgressDeadlineSeconds = &seconds
  2360  	invalidProgressDeadlineDeployment.Spec.MinReadySeconds = seconds
  2361  	errorCases["must be greater than minReadySeconds"] = invalidProgressDeadlineDeployment
  2362  
  2363  	// Must not have ephemeral containers
  2364  	invalidEphemeralContainersDeployment := validDeployment()
  2365  	invalidEphemeralContainersDeployment.Spec.Template.Spec.EphemeralContainers = []api.EphemeralContainer{{
  2366  		EphemeralContainerCommon: api.EphemeralContainerCommon{
  2367  			Name:                     "ec",
  2368  			Image:                    "image",
  2369  			ImagePullPolicy:          "IfNotPresent",
  2370  			TerminationMessagePolicy: "File"},
  2371  	}}
  2372  	errorCases["ephemeral containers not allowed"] = invalidEphemeralContainersDeployment
  2373  
  2374  	for k, v := range errorCases {
  2375  		errs := ValidateDeployment(v, corevalidation.PodValidationOptions{})
  2376  		if len(errs) == 0 {
  2377  			t.Errorf("[%s] expected failure", k)
  2378  		} else if !strings.Contains(errs[0].Error(), k) {
  2379  			t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
  2380  		}
  2381  	}
  2382  }
  2383  
  2384  func TestValidateDeploymentStatus(t *testing.T) {
  2385  	collisionCount := int32(-3)
  2386  	tests := []struct {
  2387  		name string
  2388  
  2389  		replicas           int32
  2390  		updatedReplicas    int32
  2391  		readyReplicas      int32
  2392  		availableReplicas  int32
  2393  		observedGeneration int64
  2394  		collisionCount     *int32
  2395  
  2396  		expectedErr bool
  2397  	}{{
  2398  		name:               "valid status",
  2399  		replicas:           3,
  2400  		updatedReplicas:    3,
  2401  		readyReplicas:      2,
  2402  		availableReplicas:  1,
  2403  		observedGeneration: 2,
  2404  		expectedErr:        false,
  2405  	}, {
  2406  		name:               "invalid replicas",
  2407  		replicas:           -1,
  2408  		updatedReplicas:    2,
  2409  		readyReplicas:      2,
  2410  		availableReplicas:  1,
  2411  		observedGeneration: 2,
  2412  		expectedErr:        true,
  2413  	}, {
  2414  		name:               "invalid updatedReplicas",
  2415  		replicas:           2,
  2416  		updatedReplicas:    -1,
  2417  		readyReplicas:      2,
  2418  		availableReplicas:  1,
  2419  		observedGeneration: 2,
  2420  		expectedErr:        true,
  2421  	}, {
  2422  		name:               "invalid readyReplicas",
  2423  		replicas:           3,
  2424  		readyReplicas:      -1,
  2425  		availableReplicas:  1,
  2426  		observedGeneration: 2,
  2427  		expectedErr:        true,
  2428  	}, {
  2429  		name:               "invalid availableReplicas",
  2430  		replicas:           3,
  2431  		readyReplicas:      3,
  2432  		availableReplicas:  -1,
  2433  		observedGeneration: 2,
  2434  		expectedErr:        true,
  2435  	}, {
  2436  		name:               "invalid observedGeneration",
  2437  		replicas:           3,
  2438  		readyReplicas:      3,
  2439  		availableReplicas:  3,
  2440  		observedGeneration: -1,
  2441  		expectedErr:        true,
  2442  	}, {
  2443  		name:               "updatedReplicas greater than replicas",
  2444  		replicas:           3,
  2445  		updatedReplicas:    4,
  2446  		readyReplicas:      3,
  2447  		availableReplicas:  3,
  2448  		observedGeneration: 1,
  2449  		expectedErr:        true,
  2450  	}, {
  2451  		name:               "readyReplicas greater than replicas",
  2452  		replicas:           3,
  2453  		readyReplicas:      4,
  2454  		availableReplicas:  3,
  2455  		observedGeneration: 1,
  2456  		expectedErr:        true,
  2457  	}, {
  2458  		name:               "availableReplicas greater than replicas",
  2459  		replicas:           3,
  2460  		readyReplicas:      3,
  2461  		availableReplicas:  4,
  2462  		observedGeneration: 1,
  2463  		expectedErr:        true,
  2464  	}, {
  2465  		name:               "availableReplicas greater than readyReplicas",
  2466  		replicas:           3,
  2467  		readyReplicas:      2,
  2468  		availableReplicas:  3,
  2469  		observedGeneration: 1,
  2470  		expectedErr:        true,
  2471  	}, {
  2472  		name:               "invalid collisionCount",
  2473  		replicas:           3,
  2474  		observedGeneration: 1,
  2475  		collisionCount:     &collisionCount,
  2476  		expectedErr:        true,
  2477  	},
  2478  	}
  2479  
  2480  	for _, test := range tests {
  2481  		status := apps.DeploymentStatus{
  2482  			Replicas:           test.replicas,
  2483  			UpdatedReplicas:    test.updatedReplicas,
  2484  			ReadyReplicas:      test.readyReplicas,
  2485  			AvailableReplicas:  test.availableReplicas,
  2486  			ObservedGeneration: test.observedGeneration,
  2487  			CollisionCount:     test.collisionCount,
  2488  		}
  2489  
  2490  		errs := ValidateDeploymentStatus(&status, field.NewPath("status"))
  2491  		if hasErr := len(errs) > 0; hasErr != test.expectedErr {
  2492  			errString := dump.Pretty(errs)
  2493  			t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString)
  2494  		}
  2495  	}
  2496  }
  2497  
  2498  func TestValidateDeploymentStatusUpdate(t *testing.T) {
  2499  	collisionCount := int32(1)
  2500  	otherCollisionCount := int32(2)
  2501  	tests := []struct {
  2502  		name string
  2503  
  2504  		from, to apps.DeploymentStatus
  2505  
  2506  		expectedErr bool
  2507  	}{{
  2508  		name: "increase: valid update",
  2509  		from: apps.DeploymentStatus{
  2510  			CollisionCount: nil,
  2511  		},
  2512  		to: apps.DeploymentStatus{
  2513  			CollisionCount: &collisionCount,
  2514  		},
  2515  		expectedErr: false,
  2516  	}, {
  2517  		name: "stable: valid update",
  2518  		from: apps.DeploymentStatus{
  2519  			CollisionCount: &collisionCount,
  2520  		},
  2521  		to: apps.DeploymentStatus{
  2522  			CollisionCount: &collisionCount,
  2523  		},
  2524  		expectedErr: false,
  2525  	}, {
  2526  		name: "unset: invalid update",
  2527  		from: apps.DeploymentStatus{
  2528  			CollisionCount: &collisionCount,
  2529  		},
  2530  		to: apps.DeploymentStatus{
  2531  			CollisionCount: nil,
  2532  		},
  2533  		expectedErr: true,
  2534  	}, {
  2535  		name: "decrease: invalid update",
  2536  		from: apps.DeploymentStatus{
  2537  			CollisionCount: &otherCollisionCount,
  2538  		},
  2539  		to: apps.DeploymentStatus{
  2540  			CollisionCount: &collisionCount,
  2541  		},
  2542  		expectedErr: true,
  2543  	},
  2544  	}
  2545  
  2546  	for _, test := range tests {
  2547  		meta := metav1.ObjectMeta{Name: "foo", Namespace: metav1.NamespaceDefault, ResourceVersion: "1"}
  2548  		from := &apps.Deployment{
  2549  			ObjectMeta: meta,
  2550  			Status:     test.from,
  2551  		}
  2552  		to := &apps.Deployment{
  2553  			ObjectMeta: meta,
  2554  			Status:     test.to,
  2555  		}
  2556  
  2557  		errs := ValidateDeploymentStatusUpdate(to, from)
  2558  		if hasErr := len(errs) > 0; hasErr != test.expectedErr {
  2559  			errString := dump.Pretty(errs)
  2560  			t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString)
  2561  		}
  2562  	}
  2563  }
  2564  
  2565  func validDeploymentRollback() *apps.DeploymentRollback {
  2566  	return &apps.DeploymentRollback{
  2567  		Name: "abc",
  2568  		UpdatedAnnotations: map[string]string{
  2569  			"created-by": "abc",
  2570  		},
  2571  		RollbackTo: apps.RollbackConfig{
  2572  			Revision: 1,
  2573  		},
  2574  	}
  2575  }
  2576  
  2577  func TestValidateDeploymentUpdate(t *testing.T) {
  2578  	validLabels := map[string]string{"a": "b"}
  2579  	validPodTemplate := api.PodTemplate{
  2580  		Template: api.PodTemplateSpec{
  2581  			ObjectMeta: metav1.ObjectMeta{
  2582  				Labels: validLabels,
  2583  			},
  2584  			Spec: api.PodSpec{
  2585  				RestartPolicy: api.RestartPolicyAlways,
  2586  				DNSPolicy:     api.DNSClusterFirst,
  2587  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2588  			},
  2589  		},
  2590  	}
  2591  	readWriteVolumePodTemplate := api.PodTemplate{
  2592  		Template: api.PodTemplateSpec{
  2593  			ObjectMeta: metav1.ObjectMeta{
  2594  				Labels: validLabels,
  2595  			},
  2596  			Spec: api.PodSpec{
  2597  				RestartPolicy: api.RestartPolicyAlways,
  2598  				DNSPolicy:     api.DNSClusterFirst,
  2599  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2600  				Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  2601  			},
  2602  		},
  2603  	}
  2604  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  2605  	invalidPodTemplate := api.PodTemplate{
  2606  		Template: api.PodTemplateSpec{
  2607  			Spec: api.PodSpec{
  2608  				// no containers specified
  2609  				RestartPolicy: api.RestartPolicyAlways,
  2610  				DNSPolicy:     api.DNSClusterFirst,
  2611  			},
  2612  			ObjectMeta: metav1.ObjectMeta{
  2613  				Labels: invalidLabels,
  2614  			},
  2615  		},
  2616  	}
  2617  	type depUpdateTest struct {
  2618  		old            apps.Deployment
  2619  		update         apps.Deployment
  2620  		expectedErrNum int
  2621  	}
  2622  	successCases := map[string]depUpdateTest{
  2623  		"positive replicas": {
  2624  			old: apps.Deployment{
  2625  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2626  				Spec: apps.DeploymentSpec{
  2627  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2628  					Template: validPodTemplate.Template,
  2629  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2630  				},
  2631  			},
  2632  			update: apps.Deployment{
  2633  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2634  				Spec: apps.DeploymentSpec{
  2635  					Replicas: 1,
  2636  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2637  					Template: readWriteVolumePodTemplate.Template,
  2638  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2639  				},
  2640  			},
  2641  		},
  2642  		"Read-write volume verification": {
  2643  			old: apps.Deployment{
  2644  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2645  				Spec: apps.DeploymentSpec{
  2646  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2647  					Template: validPodTemplate.Template,
  2648  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2649  				},
  2650  			},
  2651  			update: apps.Deployment{
  2652  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2653  				Spec: apps.DeploymentSpec{
  2654  					Replicas: 2,
  2655  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2656  					Template: readWriteVolumePodTemplate.Template,
  2657  					Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2658  				},
  2659  			},
  2660  		},
  2661  	}
  2662  	for testName, successCase := range successCases {
  2663  		t.Run(testName, func(t *testing.T) {
  2664  			// ResourceVersion is required for updates.
  2665  			successCase.old.ObjectMeta.ResourceVersion = "1"
  2666  			successCase.update.ObjectMeta.ResourceVersion = "2"
  2667  			// Check test setup
  2668  			if successCase.expectedErrNum > 0 {
  2669  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
  2670  			}
  2671  			if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
  2672  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  2673  			}
  2674  			// Run the tests
  2675  			if errs := ValidateDeploymentUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2676  				t.Errorf("%q expected no error, but got: %v", testName, errs)
  2677  			}
  2678  		})
  2679  		errorCases := map[string]depUpdateTest{
  2680  			"invalid selector": {
  2681  				old: apps.Deployment{
  2682  					ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2683  					Spec: apps.DeploymentSpec{
  2684  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2685  						Template: validPodTemplate.Template,
  2686  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2687  					},
  2688  				},
  2689  				update: apps.Deployment{
  2690  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2691  					Spec: apps.DeploymentSpec{
  2692  						Replicas: 2,
  2693  						Selector: &metav1.LabelSelector{MatchLabels: invalidLabels},
  2694  						Template: validPodTemplate.Template,
  2695  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2696  					},
  2697  				},
  2698  				expectedErrNum: 3,
  2699  			},
  2700  			"invalid pod": {
  2701  				old: apps.Deployment{
  2702  					ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2703  					Spec: apps.DeploymentSpec{
  2704  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2705  						Template: validPodTemplate.Template,
  2706  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2707  					},
  2708  				},
  2709  				update: apps.Deployment{
  2710  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2711  					Spec: apps.DeploymentSpec{
  2712  						Replicas: 2,
  2713  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2714  						Template: invalidPodTemplate.Template,
  2715  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2716  					},
  2717  				},
  2718  				expectedErrNum: 4,
  2719  			},
  2720  			"negative replicas": {
  2721  				old: apps.Deployment{
  2722  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2723  					Spec: apps.DeploymentSpec{
  2724  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2725  						Template: validPodTemplate.Template,
  2726  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2727  					},
  2728  				},
  2729  				update: apps.Deployment{
  2730  					ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2731  					Spec: apps.DeploymentSpec{
  2732  						Replicas: -1,
  2733  						Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2734  						Template: readWriteVolumePodTemplate.Template,
  2735  						Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
  2736  					},
  2737  				},
  2738  				expectedErrNum: 1,
  2739  			},
  2740  		}
  2741  		for testName, errorCase := range errorCases {
  2742  			t.Run(testName, func(t *testing.T) {
  2743  				// ResourceVersion is required for updates.
  2744  				errorCase.old.ObjectMeta.ResourceVersion = "1"
  2745  				errorCase.update.ObjectMeta.ResourceVersion = "2"
  2746  				// Check test setup
  2747  				if errorCase.expectedErrNum <= 0 {
  2748  					t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
  2749  				}
  2750  				if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
  2751  					t.Errorf("%q has incorrect test setup with no resource version set", testName)
  2752  				}
  2753  				// Run the tests
  2754  				if errs := ValidateDeploymentUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
  2755  					t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
  2756  				} else {
  2757  					t.Logf("(PASS) %q got errors %v", testName, errs)
  2758  				}
  2759  			})
  2760  		}
  2761  	}
  2762  }
  2763  
  2764  func TestValidateDeploymentRollback(t *testing.T) {
  2765  	noAnnotation := validDeploymentRollback()
  2766  	noAnnotation.UpdatedAnnotations = nil
  2767  	successCases := []*apps.DeploymentRollback{
  2768  		validDeploymentRollback(),
  2769  		noAnnotation,
  2770  	}
  2771  	for _, successCase := range successCases {
  2772  		if errs := ValidateDeploymentRollback(successCase); len(errs) != 0 {
  2773  			t.Errorf("expected success: %v", errs)
  2774  		}
  2775  	}
  2776  
  2777  	errorCases := map[string]*apps.DeploymentRollback{}
  2778  	invalidNoName := validDeploymentRollback()
  2779  	invalidNoName.Name = ""
  2780  	errorCases["name: Required value"] = invalidNoName
  2781  
  2782  	for k, v := range errorCases {
  2783  		errs := ValidateDeploymentRollback(v)
  2784  		if len(errs) == 0 {
  2785  			t.Errorf("[%s] expected failure", k)
  2786  		} else if !strings.Contains(errs[0].Error(), k) {
  2787  			t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
  2788  		}
  2789  	}
  2790  }
  2791  
  2792  func TestValidateReplicaSetStatus(t *testing.T) {
  2793  	tests := []struct {
  2794  		name string
  2795  
  2796  		replicas             int32
  2797  		fullyLabeledReplicas int32
  2798  		readyReplicas        int32
  2799  		availableReplicas    int32
  2800  		observedGeneration   int64
  2801  
  2802  		expectedErr bool
  2803  	}{{
  2804  		name:                 "valid status",
  2805  		replicas:             3,
  2806  		fullyLabeledReplicas: 3,
  2807  		readyReplicas:        2,
  2808  		availableReplicas:    1,
  2809  		observedGeneration:   2,
  2810  		expectedErr:          false,
  2811  	}, {
  2812  		name:                 "invalid replicas",
  2813  		replicas:             -1,
  2814  		fullyLabeledReplicas: 3,
  2815  		readyReplicas:        2,
  2816  		availableReplicas:    1,
  2817  		observedGeneration:   2,
  2818  		expectedErr:          true,
  2819  	}, {
  2820  		name:                 "invalid fullyLabeledReplicas",
  2821  		replicas:             3,
  2822  		fullyLabeledReplicas: -1,
  2823  		readyReplicas:        2,
  2824  		availableReplicas:    1,
  2825  		observedGeneration:   2,
  2826  		expectedErr:          true,
  2827  	}, {
  2828  		name:                 "invalid readyReplicas",
  2829  		replicas:             3,
  2830  		fullyLabeledReplicas: 3,
  2831  		readyReplicas:        -1,
  2832  		availableReplicas:    1,
  2833  		observedGeneration:   2,
  2834  		expectedErr:          true,
  2835  	}, {
  2836  		name:                 "invalid availableReplicas",
  2837  		replicas:             3,
  2838  		fullyLabeledReplicas: 3,
  2839  		readyReplicas:        3,
  2840  		availableReplicas:    -1,
  2841  		observedGeneration:   2,
  2842  		expectedErr:          true,
  2843  	}, {
  2844  		name:                 "invalid observedGeneration",
  2845  		replicas:             3,
  2846  		fullyLabeledReplicas: 3,
  2847  		readyReplicas:        3,
  2848  		availableReplicas:    3,
  2849  		observedGeneration:   -1,
  2850  		expectedErr:          true,
  2851  	}, {
  2852  		name:                 "fullyLabeledReplicas greater than replicas",
  2853  		replicas:             3,
  2854  		fullyLabeledReplicas: 4,
  2855  		readyReplicas:        3,
  2856  		availableReplicas:    3,
  2857  		observedGeneration:   1,
  2858  		expectedErr:          true,
  2859  	}, {
  2860  		name:                 "readyReplicas greater than replicas",
  2861  		replicas:             3,
  2862  		fullyLabeledReplicas: 3,
  2863  		readyReplicas:        4,
  2864  		availableReplicas:    3,
  2865  		observedGeneration:   1,
  2866  		expectedErr:          true,
  2867  	}, {
  2868  		name:                 "availableReplicas greater than replicas",
  2869  		replicas:             3,
  2870  		fullyLabeledReplicas: 3,
  2871  		readyReplicas:        3,
  2872  		availableReplicas:    4,
  2873  		observedGeneration:   1,
  2874  		expectedErr:          true,
  2875  	}, {
  2876  		name:                 "availableReplicas greater than readyReplicas",
  2877  		replicas:             3,
  2878  		fullyLabeledReplicas: 3,
  2879  		readyReplicas:        2,
  2880  		availableReplicas:    3,
  2881  		observedGeneration:   1,
  2882  		expectedErr:          true,
  2883  	},
  2884  	}
  2885  
  2886  	for _, test := range tests {
  2887  		status := apps.ReplicaSetStatus{
  2888  			Replicas:             test.replicas,
  2889  			FullyLabeledReplicas: test.fullyLabeledReplicas,
  2890  			ReadyReplicas:        test.readyReplicas,
  2891  			AvailableReplicas:    test.availableReplicas,
  2892  			ObservedGeneration:   test.observedGeneration,
  2893  		}
  2894  
  2895  		if hasErr := len(ValidateReplicaSetStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
  2896  			t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
  2897  		}
  2898  	}
  2899  }
  2900  
  2901  func TestValidateReplicaSetStatusUpdate(t *testing.T) {
  2902  	validLabels := map[string]string{"a": "b"}
  2903  	validPodTemplate := api.PodTemplate{
  2904  		Template: api.PodTemplateSpec{
  2905  			ObjectMeta: metav1.ObjectMeta{
  2906  				Labels: validLabels,
  2907  			},
  2908  			Spec: api.PodSpec{
  2909  				RestartPolicy: api.RestartPolicyAlways,
  2910  				DNSPolicy:     api.DNSClusterFirst,
  2911  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2912  			},
  2913  		},
  2914  	}
  2915  	type rcUpdateTest struct {
  2916  		old    apps.ReplicaSet
  2917  		update apps.ReplicaSet
  2918  	}
  2919  	successCases := []rcUpdateTest{{
  2920  		old: apps.ReplicaSet{
  2921  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2922  			Spec: apps.ReplicaSetSpec{
  2923  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2924  				Template: validPodTemplate.Template,
  2925  			},
  2926  			Status: apps.ReplicaSetStatus{
  2927  				Replicas: 2,
  2928  			},
  2929  		},
  2930  		update: apps.ReplicaSet{
  2931  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2932  			Spec: apps.ReplicaSetSpec{
  2933  				Replicas: 3,
  2934  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2935  				Template: validPodTemplate.Template,
  2936  			},
  2937  			Status: apps.ReplicaSetStatus{
  2938  				Replicas: 4,
  2939  			},
  2940  		},
  2941  	},
  2942  	}
  2943  	for _, successCase := range successCases {
  2944  		successCase.old.ObjectMeta.ResourceVersion = "1"
  2945  		successCase.update.ObjectMeta.ResourceVersion = "1"
  2946  		if errs := ValidateReplicaSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
  2947  			t.Errorf("expected success: %v", errs)
  2948  		}
  2949  	}
  2950  	errorCases := map[string]rcUpdateTest{
  2951  		"negative replicas": {
  2952  			old: apps.ReplicaSet{
  2953  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  2954  				Spec: apps.ReplicaSetSpec{
  2955  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2956  					Template: validPodTemplate.Template,
  2957  				},
  2958  				Status: apps.ReplicaSetStatus{
  2959  					Replicas: 3,
  2960  				},
  2961  			},
  2962  			update: apps.ReplicaSet{
  2963  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  2964  				Spec: apps.ReplicaSetSpec{
  2965  					Replicas: 2,
  2966  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  2967  					Template: validPodTemplate.Template,
  2968  				},
  2969  				Status: apps.ReplicaSetStatus{
  2970  					Replicas: -3,
  2971  				},
  2972  			},
  2973  		},
  2974  	}
  2975  	for testName, errorCase := range errorCases {
  2976  		if errs := ValidateReplicaSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
  2977  			t.Errorf("expected failure: %s", testName)
  2978  		}
  2979  	}
  2980  
  2981  }
  2982  
  2983  func TestValidateReplicaSetUpdate(t *testing.T) {
  2984  	validLabels := map[string]string{"a": "b"}
  2985  	validPodTemplate := api.PodTemplate{
  2986  		Template: api.PodTemplateSpec{
  2987  			ObjectMeta: metav1.ObjectMeta{
  2988  				Labels: validLabels,
  2989  			},
  2990  			Spec: api.PodSpec{
  2991  				RestartPolicy: api.RestartPolicyAlways,
  2992  				DNSPolicy:     api.DNSClusterFirst,
  2993  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2994  			},
  2995  		},
  2996  	}
  2997  	readWriteVolumePodTemplate := api.PodTemplate{
  2998  		Template: api.PodTemplateSpec{
  2999  			ObjectMeta: metav1.ObjectMeta{
  3000  				Labels: validLabels,
  3001  			},
  3002  			Spec: api.PodSpec{
  3003  				RestartPolicy: api.RestartPolicyAlways,
  3004  				DNSPolicy:     api.DNSClusterFirst,
  3005  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3006  				Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  3007  			},
  3008  		},
  3009  	}
  3010  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  3011  	invalidPodTemplate := api.PodTemplate{
  3012  		Template: api.PodTemplateSpec{
  3013  			Spec: api.PodSpec{
  3014  				RestartPolicy: api.RestartPolicyAlways,
  3015  				DNSPolicy:     api.DNSClusterFirst,
  3016  			},
  3017  			ObjectMeta: metav1.ObjectMeta{
  3018  				Labels: invalidLabels,
  3019  			},
  3020  		},
  3021  	}
  3022  	type rcUpdateTest struct {
  3023  		old            apps.ReplicaSet
  3024  		update         apps.ReplicaSet
  3025  		expectedErrNum int
  3026  	}
  3027  	successCases := map[string]rcUpdateTest{
  3028  		"positive replicas": {
  3029  			old: apps.ReplicaSet{
  3030  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3031  				Spec: apps.ReplicaSetSpec{
  3032  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3033  					Template: validPodTemplate.Template,
  3034  				},
  3035  			},
  3036  			update: apps.ReplicaSet{
  3037  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3038  				Spec: apps.ReplicaSetSpec{
  3039  					Replicas: 3,
  3040  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3041  					Template: validPodTemplate.Template,
  3042  				},
  3043  			},
  3044  		},
  3045  		"Read-write volume verification": {
  3046  			old: apps.ReplicaSet{
  3047  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3048  				Spec: apps.ReplicaSetSpec{
  3049  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3050  					Template: validPodTemplate.Template,
  3051  				},
  3052  			},
  3053  			update: apps.ReplicaSet{
  3054  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3055  				Spec: apps.ReplicaSetSpec{
  3056  					Replicas: 3,
  3057  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3058  					Template: readWriteVolumePodTemplate.Template,
  3059  				},
  3060  			},
  3061  		},
  3062  	}
  3063  	for testName, successCase := range successCases {
  3064  		t.Run(testName, func(t *testing.T) {
  3065  			// ResourceVersion is required for updates.
  3066  			successCase.old.ObjectMeta.ResourceVersion = "1"
  3067  			successCase.update.ObjectMeta.ResourceVersion = "2"
  3068  			// Check test setup
  3069  			if successCase.expectedErrNum > 0 {
  3070  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
  3071  			}
  3072  			if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
  3073  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  3074  			}
  3075  			// Run the tests
  3076  			if errs := ValidateReplicaSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  3077  				t.Errorf("%q expected no error, but got: %v", testName, errs)
  3078  			}
  3079  		})
  3080  	}
  3081  	errorCases := map[string]rcUpdateTest{
  3082  		"invalid selector": {
  3083  			old: apps.ReplicaSet{
  3084  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3085  				Spec: apps.ReplicaSetSpec{
  3086  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3087  					Template: validPodTemplate.Template,
  3088  				},
  3089  			},
  3090  			update: apps.ReplicaSet{
  3091  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3092  				Spec: apps.ReplicaSetSpec{
  3093  					Replicas: 2,
  3094  					Selector: &metav1.LabelSelector{MatchLabels: invalidLabels},
  3095  					Template: validPodTemplate.Template,
  3096  				},
  3097  			},
  3098  			expectedErrNum: 3,
  3099  		},
  3100  		"invalid pod": {
  3101  			old: apps.ReplicaSet{
  3102  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3103  				Spec: apps.ReplicaSetSpec{
  3104  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3105  					Template: validPodTemplate.Template,
  3106  				},
  3107  			},
  3108  			update: apps.ReplicaSet{
  3109  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3110  				Spec: apps.ReplicaSetSpec{
  3111  					Replicas: 2,
  3112  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3113  					Template: invalidPodTemplate.Template,
  3114  				},
  3115  			},
  3116  			expectedErrNum: 4,
  3117  		},
  3118  		"negative replicas": {
  3119  			old: apps.ReplicaSet{
  3120  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3121  				Spec: apps.ReplicaSetSpec{
  3122  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3123  					Template: validPodTemplate.Template,
  3124  				},
  3125  			},
  3126  			update: apps.ReplicaSet{
  3127  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3128  				Spec: apps.ReplicaSetSpec{
  3129  					Replicas: -1,
  3130  					Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3131  					Template: validPodTemplate.Template,
  3132  				},
  3133  			},
  3134  			expectedErrNum: 1,
  3135  		},
  3136  	}
  3137  	for testName, errorCase := range errorCases {
  3138  		t.Run(testName, func(t *testing.T) {
  3139  			// ResourceVersion is required for updates.
  3140  			errorCase.old.ObjectMeta.ResourceVersion = "1"
  3141  			errorCase.update.ObjectMeta.ResourceVersion = "2"
  3142  			// Check test setup
  3143  			if errorCase.expectedErrNum <= 0 {
  3144  				t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
  3145  			}
  3146  			if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
  3147  				t.Errorf("%q has incorrect test setup with no resource version set", testName)
  3148  			}
  3149  			// Run the tests
  3150  			if errs := ValidateReplicaSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
  3151  				t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
  3152  			} else {
  3153  				t.Logf("(PASS) %q got errors %v", testName, errs)
  3154  			}
  3155  		})
  3156  	}
  3157  }
  3158  
  3159  func TestValidateReplicaSet(t *testing.T) {
  3160  	validLabels := map[string]string{"a": "b"}
  3161  	validPodTemplate := api.PodTemplate{
  3162  		Template: api.PodTemplateSpec{
  3163  			ObjectMeta: metav1.ObjectMeta{
  3164  				Labels: validLabels,
  3165  			},
  3166  			Spec: api.PodSpec{
  3167  				RestartPolicy: api.RestartPolicyAlways,
  3168  				DNSPolicy:     api.DNSClusterFirst,
  3169  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3170  			},
  3171  		},
  3172  	}
  3173  	validHostNetPodTemplate := api.PodTemplate{
  3174  		Template: api.PodTemplateSpec{
  3175  			ObjectMeta: metav1.ObjectMeta{
  3176  				Labels: validLabels,
  3177  			},
  3178  			Spec: api.PodSpec{
  3179  				SecurityContext: &api.PodSecurityContext{
  3180  					HostNetwork: true,
  3181  				},
  3182  				RestartPolicy: api.RestartPolicyAlways,
  3183  				DNSPolicy:     api.DNSClusterFirst,
  3184  				Containers: []api.Container{{
  3185  					Name:                     "abc",
  3186  					Image:                    "image",
  3187  					ImagePullPolicy:          "IfNotPresent",
  3188  					TerminationMessagePolicy: api.TerminationMessageReadFile,
  3189  					Ports: []api.ContainerPort{{
  3190  						ContainerPort: 12345,
  3191  						Protocol:      api.ProtocolTCP,
  3192  					}},
  3193  				}},
  3194  			},
  3195  		},
  3196  	}
  3197  	readWriteVolumePodTemplate := api.PodTemplate{
  3198  		Template: api.PodTemplateSpec{
  3199  			ObjectMeta: metav1.ObjectMeta{
  3200  				Labels: validLabels,
  3201  			},
  3202  			Spec: api.PodSpec{
  3203  				Volumes:       []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
  3204  				RestartPolicy: api.RestartPolicyAlways,
  3205  				DNSPolicy:     api.DNSClusterFirst,
  3206  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3207  			},
  3208  		},
  3209  	}
  3210  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
  3211  	invalidPodTemplate := api.PodTemplate{
  3212  		Template: api.PodTemplateSpec{
  3213  			Spec: api.PodSpec{
  3214  				RestartPolicy: api.RestartPolicyAlways,
  3215  				DNSPolicy:     api.DNSClusterFirst,
  3216  			},
  3217  			ObjectMeta: metav1.ObjectMeta{
  3218  				Labels: invalidLabels,
  3219  			},
  3220  		},
  3221  	}
  3222  	successCases := []apps.ReplicaSet{{
  3223  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3224  		Spec: apps.ReplicaSetSpec{
  3225  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3226  			Template: validPodTemplate.Template,
  3227  		},
  3228  	}, {
  3229  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
  3230  		Spec: apps.ReplicaSetSpec{
  3231  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3232  			Template: validPodTemplate.Template,
  3233  		},
  3234  	}, {
  3235  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
  3236  		Spec: apps.ReplicaSetSpec{
  3237  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3238  			Template: validHostNetPodTemplate.Template,
  3239  		},
  3240  	}, {
  3241  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
  3242  		Spec: apps.ReplicaSetSpec{
  3243  			Replicas: 1,
  3244  			Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3245  			Template: readWriteVolumePodTemplate.Template,
  3246  		},
  3247  	},
  3248  	}
  3249  	for _, successCase := range successCases {
  3250  		if errs := ValidateReplicaSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  3251  			t.Errorf("expected success: %v", errs)
  3252  		}
  3253  	}
  3254  
  3255  	errorCases := map[string]apps.ReplicaSet{
  3256  		"zero-length ID": {
  3257  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
  3258  			Spec: apps.ReplicaSetSpec{
  3259  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3260  				Template: validPodTemplate.Template,
  3261  			},
  3262  		},
  3263  		"missing-namespace": {
  3264  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
  3265  			Spec: apps.ReplicaSetSpec{
  3266  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3267  				Template: validPodTemplate.Template,
  3268  			},
  3269  		},
  3270  		"empty selector": {
  3271  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3272  			Spec: apps.ReplicaSetSpec{
  3273  				Template: validPodTemplate.Template,
  3274  			},
  3275  		},
  3276  		"selector_doesnt_match": {
  3277  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3278  			Spec: apps.ReplicaSetSpec{
  3279  				Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  3280  				Template: validPodTemplate.Template,
  3281  			},
  3282  		},
  3283  		"invalid manifest": {
  3284  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3285  			Spec: apps.ReplicaSetSpec{
  3286  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3287  			},
  3288  		},
  3289  		"read-write persistent disk with > 1 pod": {
  3290  			ObjectMeta: metav1.ObjectMeta{Name: "abc"},
  3291  			Spec: apps.ReplicaSetSpec{
  3292  				Replicas: 2,
  3293  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3294  				Template: readWriteVolumePodTemplate.Template,
  3295  			},
  3296  		},
  3297  		"negative_replicas": {
  3298  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  3299  			Spec: apps.ReplicaSetSpec{
  3300  				Replicas: -1,
  3301  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3302  			},
  3303  		},
  3304  		"invalid_label": {
  3305  			ObjectMeta: metav1.ObjectMeta{
  3306  				Name:      "abc-123",
  3307  				Namespace: metav1.NamespaceDefault,
  3308  				Labels: map[string]string{
  3309  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  3310  				},
  3311  			},
  3312  			Spec: apps.ReplicaSetSpec{
  3313  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3314  				Template: validPodTemplate.Template,
  3315  			},
  3316  		},
  3317  		"invalid_label 2": {
  3318  			ObjectMeta: metav1.ObjectMeta{
  3319  				Name:      "abc-123",
  3320  				Namespace: metav1.NamespaceDefault,
  3321  				Labels: map[string]string{
  3322  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  3323  				},
  3324  			},
  3325  			Spec: apps.ReplicaSetSpec{
  3326  				Template: invalidPodTemplate.Template,
  3327  			},
  3328  		},
  3329  		"invalid_annotation": {
  3330  			ObjectMeta: metav1.ObjectMeta{
  3331  				Name:      "abc-123",
  3332  				Namespace: metav1.NamespaceDefault,
  3333  				Annotations: map[string]string{
  3334  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
  3335  				},
  3336  			},
  3337  			Spec: apps.ReplicaSetSpec{
  3338  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3339  				Template: validPodTemplate.Template,
  3340  			},
  3341  		},
  3342  		"invalid restart policy 1": {
  3343  			ObjectMeta: metav1.ObjectMeta{
  3344  				Name:      "abc-123",
  3345  				Namespace: metav1.NamespaceDefault,
  3346  			},
  3347  			Spec: apps.ReplicaSetSpec{
  3348  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3349  				Template: api.PodTemplateSpec{
  3350  					Spec: api.PodSpec{
  3351  						RestartPolicy: api.RestartPolicyOnFailure,
  3352  						DNSPolicy:     api.DNSClusterFirst,
  3353  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3354  					},
  3355  					ObjectMeta: metav1.ObjectMeta{
  3356  						Labels: validLabels,
  3357  					},
  3358  				},
  3359  			},
  3360  		},
  3361  		"invalid restart policy 2": {
  3362  			ObjectMeta: metav1.ObjectMeta{
  3363  				Name:      "abc-123",
  3364  				Namespace: metav1.NamespaceDefault,
  3365  			},
  3366  			Spec: apps.ReplicaSetSpec{
  3367  				Selector: &metav1.LabelSelector{MatchLabels: validLabels},
  3368  				Template: api.PodTemplateSpec{
  3369  					Spec: api.PodSpec{
  3370  						RestartPolicy: api.RestartPolicyNever,
  3371  						DNSPolicy:     api.DNSClusterFirst,
  3372  						Containers:    []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  3373  					},
  3374  					ObjectMeta: metav1.ObjectMeta{
  3375  						Labels: validLabels,
  3376  					},
  3377  				},
  3378  			},
  3379  		},
  3380  	}
  3381  	for k, v := range errorCases {
  3382  		errs := ValidateReplicaSet(&v, corevalidation.PodValidationOptions{})
  3383  		if len(errs) == 0 {
  3384  			t.Errorf("expected failure for %s", k)
  3385  		}
  3386  		for i := range errs {
  3387  			field := errs[i].Field
  3388  			if !strings.HasPrefix(field, "spec.template.") &&
  3389  				field != "metadata.name" &&
  3390  				field != "metadata.namespace" &&
  3391  				field != "spec.selector" &&
  3392  				field != "spec.template" &&
  3393  				field != "GCEPersistentDisk.ReadOnly" &&
  3394  				field != "spec.replicas" &&
  3395  				field != "spec.template.labels" &&
  3396  				field != "metadata.annotations" &&
  3397  				field != "metadata.labels" &&
  3398  				field != "status.replicas" {
  3399  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
  3400  			}
  3401  		}
  3402  	}
  3403  }
  3404  
  3405  func TestDaemonSetUpdateMaxSurge(t *testing.T) {
  3406  	testCases := map[string]struct {
  3407  		ds          *apps.RollingUpdateDaemonSet
  3408  		expectError bool
  3409  	}{
  3410  		"invalid: unset": {
  3411  			ds:          &apps.RollingUpdateDaemonSet{},
  3412  			expectError: true,
  3413  		},
  3414  		"invalid: zero percent": {
  3415  			ds: &apps.RollingUpdateDaemonSet{
  3416  				MaxUnavailable: intstr.FromString("0%"),
  3417  			},
  3418  			expectError: true,
  3419  		},
  3420  		"invalid: zero": {
  3421  			ds: &apps.RollingUpdateDaemonSet{
  3422  				MaxUnavailable: intstr.FromInt32(0),
  3423  			},
  3424  			expectError: true,
  3425  		},
  3426  		"valid: one": {
  3427  			ds: &apps.RollingUpdateDaemonSet{
  3428  				MaxUnavailable: intstr.FromInt32(1),
  3429  			},
  3430  		},
  3431  		"valid: one percent": {
  3432  			ds: &apps.RollingUpdateDaemonSet{
  3433  				MaxUnavailable: intstr.FromString("1%"),
  3434  			},
  3435  		},
  3436  		"valid: 100%": {
  3437  			ds: &apps.RollingUpdateDaemonSet{
  3438  				MaxUnavailable: intstr.FromString("100%"),
  3439  			},
  3440  		},
  3441  		"invalid: greater than 100%": {
  3442  			ds: &apps.RollingUpdateDaemonSet{
  3443  				MaxUnavailable: intstr.FromString("101%"),
  3444  			},
  3445  			expectError: true,
  3446  		},
  3447  
  3448  		"valid: surge and unavailable set": {
  3449  			ds: &apps.RollingUpdateDaemonSet{
  3450  				MaxUnavailable: intstr.FromString("1%"),
  3451  				MaxSurge:       intstr.FromString("1%"),
  3452  			},
  3453  			expectError: true,
  3454  		},
  3455  
  3456  		"invalid: surge enabled, unavailable zero percent": {
  3457  			ds: &apps.RollingUpdateDaemonSet{
  3458  				MaxUnavailable: intstr.FromString("0%"),
  3459  			},
  3460  			expectError: true,
  3461  		},
  3462  		"invalid: surge enabled, unavailable zero": {
  3463  			ds: &apps.RollingUpdateDaemonSet{
  3464  				MaxUnavailable: intstr.FromInt32(0),
  3465  			},
  3466  			expectError: true,
  3467  		},
  3468  		"valid: surge enabled, unavailable one": {
  3469  			ds: &apps.RollingUpdateDaemonSet{
  3470  				MaxUnavailable: intstr.FromInt32(1),
  3471  			},
  3472  		},
  3473  		"valid: surge enabled, unavailable one percent": {
  3474  			ds: &apps.RollingUpdateDaemonSet{
  3475  				MaxUnavailable: intstr.FromString("1%"),
  3476  			},
  3477  		},
  3478  		"valid: surge enabled, unavailable 100%": {
  3479  			ds: &apps.RollingUpdateDaemonSet{
  3480  				MaxUnavailable: intstr.FromString("100%"),
  3481  			},
  3482  		},
  3483  		"invalid: surge enabled, unavailable greater than 100%": {
  3484  			ds: &apps.RollingUpdateDaemonSet{
  3485  				MaxUnavailable: intstr.FromString("101%"),
  3486  			},
  3487  			expectError: true,
  3488  		},
  3489  
  3490  		"invalid: surge enabled, surge zero percent": {
  3491  			ds: &apps.RollingUpdateDaemonSet{
  3492  				MaxSurge: intstr.FromString("0%"),
  3493  			},
  3494  			expectError: true,
  3495  		},
  3496  		"invalid: surge enabled, surge zero": {
  3497  			ds: &apps.RollingUpdateDaemonSet{
  3498  				MaxSurge: intstr.FromInt32(0),
  3499  			},
  3500  			expectError: true,
  3501  		},
  3502  		"valid: surge enabled, surge one": {
  3503  			ds: &apps.RollingUpdateDaemonSet{
  3504  				MaxSurge: intstr.FromInt32(1),
  3505  			},
  3506  		},
  3507  		"valid: surge enabled, surge one percent": {
  3508  			ds: &apps.RollingUpdateDaemonSet{
  3509  				MaxSurge: intstr.FromString("1%"),
  3510  			},
  3511  		},
  3512  		"valid: surge enabled, surge 100%": {
  3513  			ds: &apps.RollingUpdateDaemonSet{
  3514  				MaxSurge: intstr.FromString("100%"),
  3515  			},
  3516  		},
  3517  		"invalid: surge enabled, surge greater than 100%": {
  3518  			ds: &apps.RollingUpdateDaemonSet{
  3519  				MaxSurge: intstr.FromString("101%"),
  3520  			},
  3521  			expectError: true,
  3522  		},
  3523  
  3524  		"invalid: surge enabled, 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, surge and unavailable zero percent": {
  3533  			ds: &apps.RollingUpdateDaemonSet{
  3534  				MaxUnavailable: intstr.FromString("0%"),
  3535  				MaxSurge:       intstr.FromString("0%"),
  3536  			},
  3537  			expectError: true,
  3538  		},
  3539  		"invalid: surge enabled, surge and unavailable zero": {
  3540  			ds: &apps.RollingUpdateDaemonSet{
  3541  				MaxUnavailable: intstr.FromInt32(0),
  3542  				MaxSurge:       intstr.FromInt32(0),
  3543  			},
  3544  			expectError: true,
  3545  		},
  3546  		"invalid: surge enabled, surge and unavailable mixed zero": {
  3547  			ds: &apps.RollingUpdateDaemonSet{
  3548  				MaxUnavailable: intstr.FromInt32(0),
  3549  				MaxSurge:       intstr.FromString("0%"),
  3550  			},
  3551  			expectError: true,
  3552  		},
  3553  	}
  3554  	for tcName, tc := range testCases {
  3555  		t.Run(tcName, func(t *testing.T) {
  3556  			errs := ValidateRollingUpdateDaemonSet(tc.ds, field.NewPath("spec", "updateStrategy", "rollingUpdate"))
  3557  			if tc.expectError && len(errs) == 0 {
  3558  				t.Errorf("Unexpected success")
  3559  			}
  3560  			if !tc.expectError && len(errs) != 0 {
  3561  				t.Errorf("Unexpected error(s): %v", errs)
  3562  			}
  3563  		})
  3564  	}
  3565  }