k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/apps/validation/validation.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 "strconv" 22 23 apiequality "k8s.io/apimachinery/pkg/api/equality" 24 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/util/intstr" 29 "k8s.io/apimachinery/pkg/util/validation" 30 "k8s.io/apimachinery/pkg/util/validation/field" 31 "k8s.io/kubernetes/pkg/apis/apps" 32 api "k8s.io/kubernetes/pkg/apis/core" 33 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 34 ) 35 36 // ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid. 37 // Prefix indicates this name will be used as part of generation, in which case 38 // trailing dashes are allowed. 39 func ValidateStatefulSetName(name string, prefix bool) []string { 40 // TODO: Validate that there's room for the suffix inserted by the pods. 41 // Currently this is just "-index". In the future we may allow a user 42 // specified list of suffixes and we need to validate the longest one. 43 return apimachineryvalidation.NameIsDNSLabel(name, prefix) 44 } 45 46 // ValidatePodTemplateSpecForStatefulSet validates the given template and ensures that it is in accordance with the desired selector. 47 func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 48 allErrs := field.ErrorList{} 49 if template == nil { 50 allErrs = append(allErrs, field.Required(fldPath, "")) 51 } else { 52 if !selector.Empty() { 53 // Verify that the StatefulSet selector matches the labels in template. 54 labels := labels.Set(template.Labels) 55 if !selector.Matches(labels) { 56 allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) 57 } 58 } 59 // TODO: Add validation for PodSpec, currently this will check volumes, which we know will 60 // fail. We should really check that the union of the given volumes and volumeClaims match 61 // volume mounts in the containers. 62 // allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...) 63 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(template.Labels, fldPath.Child("labels"))...) 64 allErrs = append(allErrs, apivalidation.ValidateAnnotations(template.Annotations, fldPath.Child("annotations"))...) 65 allErrs = append(allErrs, apivalidation.ValidatePodSpecificAnnotations(template.Annotations, &template.Spec, fldPath.Child("annotations"), opts)...) 66 } 67 return allErrs 68 } 69 70 func ValidatePersistentVolumeClaimRetentionPolicyType(policy apps.PersistentVolumeClaimRetentionPolicyType, fldPath *field.Path) field.ErrorList { 71 var allErrs field.ErrorList 72 switch policy { 73 case apps.RetainPersistentVolumeClaimRetentionPolicyType: 74 case apps.DeletePersistentVolumeClaimRetentionPolicyType: 75 default: 76 allErrs = append(allErrs, field.NotSupported(fldPath, policy, []string{string(apps.RetainPersistentVolumeClaimRetentionPolicyType), string(apps.DeletePersistentVolumeClaimRetentionPolicyType)})) 77 } 78 return allErrs 79 } 80 81 func ValidatePersistentVolumeClaimRetentionPolicy(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy, fldPath *field.Path) field.ErrorList { 82 var allErrs field.ErrorList 83 if policy != nil { 84 allErrs = append(allErrs, ValidatePersistentVolumeClaimRetentionPolicyType(policy.WhenDeleted, fldPath.Child("whenDeleted"))...) 85 allErrs = append(allErrs, ValidatePersistentVolumeClaimRetentionPolicyType(policy.WhenScaled, fldPath.Child("whenScaled"))...) 86 } 87 return allErrs 88 } 89 90 // ValidateStatefulSetSpec tests if required fields in the StatefulSet spec are set. 91 func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 92 allErrs := field.ErrorList{} 93 94 switch spec.PodManagementPolicy { 95 case "": 96 allErrs = append(allErrs, field.Required(fldPath.Child("podManagementPolicy"), "")) 97 case apps.OrderedReadyPodManagement, apps.ParallelPodManagement: 98 default: 99 allErrs = append(allErrs, field.Invalid(fldPath.Child("podManagementPolicy"), spec.PodManagementPolicy, fmt.Sprintf("must be '%s' or '%s'", apps.OrderedReadyPodManagement, apps.ParallelPodManagement))) 100 } 101 102 switch spec.UpdateStrategy.Type { 103 case "": 104 allErrs = append(allErrs, field.Required(fldPath.Child("updateStrategy"), "")) 105 case apps.OnDeleteStatefulSetStrategyType: 106 if spec.UpdateStrategy.RollingUpdate != nil { 107 allErrs = append( 108 allErrs, 109 field.Invalid( 110 fldPath.Child("updateStrategy").Child("rollingUpdate"), 111 spec.UpdateStrategy.RollingUpdate, 112 fmt.Sprintf("only allowed for updateStrategy '%s'", apps.RollingUpdateStatefulSetStrategyType))) 113 } 114 case apps.RollingUpdateStatefulSetStrategyType: 115 if spec.UpdateStrategy.RollingUpdate != nil { 116 allErrs = append(allErrs, validateRollingUpdateStatefulSet(spec.UpdateStrategy.RollingUpdate, fldPath.Child("updateStrategy", "rollingUpdate"))...) 117 118 } 119 default: 120 allErrs = append(allErrs, 121 field.Invalid(fldPath.Child("updateStrategy"), spec.UpdateStrategy, 122 fmt.Sprintf("must be '%s' or '%s'", 123 apps.RollingUpdateStatefulSetStrategyType, 124 apps.OnDeleteStatefulSetStrategyType))) 125 } 126 127 allErrs = append(allErrs, ValidatePersistentVolumeClaimRetentionPolicy(spec.PersistentVolumeClaimRetentionPolicy, fldPath.Child("persistentVolumeClaimRetentionPolicy"))...) 128 129 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) 130 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 131 if spec.Ordinals != nil { 132 replicaStartOrdinal := spec.Ordinals.Start 133 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(replicaStartOrdinal), fldPath.Child("ordinals.start"))...) 134 } 135 136 if spec.Selector == nil { 137 allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) 138 } else { 139 // validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below 140 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...) 141 if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { 142 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for statefulset")) 143 } 144 } 145 146 selector, err := metav1.LabelSelectorAsSelector(spec.Selector) 147 if err != nil { 148 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "")) 149 } else { 150 allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(&spec.Template, selector, fldPath.Child("template"), opts)...) 151 } 152 153 if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { 154 allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) 155 } 156 if spec.Template.Spec.ActiveDeadlineSeconds != nil { 157 allErrs = append(allErrs, field.Forbidden(fldPath.Child("template", "spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in StatefulSet is not Supported")) 158 } 159 160 return allErrs 161 } 162 163 // ValidateStatefulSet validates a StatefulSet. 164 func ValidateStatefulSet(statefulSet *apps.StatefulSet, opts apivalidation.PodValidationOptions) field.ErrorList { 165 allErrs := apivalidation.ValidateObjectMeta(&statefulSet.ObjectMeta, true, ValidateStatefulSetName, field.NewPath("metadata")) 166 allErrs = append(allErrs, ValidateStatefulSetSpec(&statefulSet.Spec, field.NewPath("spec"), opts)...) 167 return allErrs 168 } 169 170 // ValidateStatefulSetUpdate tests if required fields in the StatefulSet are set. 171 func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, opts apivalidation.PodValidationOptions) field.ErrorList { 172 // First, validate that the new statefulset is valid. Don't call 173 // ValidateStatefulSet() because we don't want to revalidate the name on 174 // update. This is important here because we used to allow DNS subdomain 175 // for name, but that can't actually create pods. The only reasonable 176 // thing to do it delete such an instance, but if there is a finalizer, it 177 // would need to pass update validation. Name can't change anyway. 178 allErrs := apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata")) 179 allErrs = append(allErrs, ValidateStatefulSetSpec(&statefulSet.Spec, field.NewPath("spec"), opts)...) 180 181 // statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this 182 // deep copy right away. This avoids mutating our inputs 183 newStatefulSetClone := statefulSet.DeepCopy() 184 newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone 185 newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone 186 newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone 187 newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone 188 newStatefulSetClone.Spec.Ordinals = oldStatefulSet.Spec.Ordinals // +k8s:verify-mutation:reason=clone 189 190 newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone 191 if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) { 192 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden")) 193 } 194 195 return allErrs 196 } 197 198 // ValidateStatefulSetStatus validates a StatefulSetStatus. 199 func ValidateStatefulSetStatus(status *apps.StatefulSetStatus, fieldPath *field.Path) field.ErrorList { 200 allErrs := field.ErrorList{} 201 202 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fieldPath.Child("replicas"))...) 203 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fieldPath.Child("readyReplicas"))...) 204 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), fieldPath.Child("currentReplicas"))...) 205 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fieldPath.Child("updatedReplicas"))...) 206 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fieldPath.Child("availableReplicas"))...) 207 if status.ObservedGeneration != nil { 208 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.ObservedGeneration), fieldPath.Child("observedGeneration"))...) 209 } 210 if status.CollisionCount != nil { 211 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fieldPath.Child("collisionCount"))...) 212 } 213 214 msg := "cannot be greater than status.replicas" 215 if status.ReadyReplicas > status.Replicas { 216 allErrs = append(allErrs, field.Invalid(fieldPath.Child("readyReplicas"), status.ReadyReplicas, msg)) 217 } 218 if status.CurrentReplicas > status.Replicas { 219 allErrs = append(allErrs, field.Invalid(fieldPath.Child("currentReplicas"), status.CurrentReplicas, msg)) 220 } 221 if status.UpdatedReplicas > status.Replicas { 222 allErrs = append(allErrs, field.Invalid(fieldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg)) 223 } 224 if status.AvailableReplicas > status.Replicas { 225 allErrs = append(allErrs, field.Invalid(fieldPath.Child("availableReplicas"), status.AvailableReplicas, msg)) 226 } 227 if status.AvailableReplicas > status.ReadyReplicas { 228 allErrs = append(allErrs, field.Invalid(fieldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than status.readyReplicas")) 229 } 230 return allErrs 231 } 232 233 // ValidateStatefulSetStatusUpdate tests if required fields in the StatefulSet are set. 234 func ValidateStatefulSetStatusUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList { 235 allErrs := field.ErrorList{} 236 allErrs = append(allErrs, ValidateStatefulSetStatus(&statefulSet.Status, field.NewPath("status"))...) 237 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))...) 238 // TODO: Validate status. 239 if apivalidation.IsDecremented(statefulSet.Status.CollisionCount, oldStatefulSet.Status.CollisionCount) { 240 value := int32(0) 241 if statefulSet.Status.CollisionCount != nil { 242 value = *statefulSet.Status.CollisionCount 243 } 244 allErrs = append(allErrs, field.Invalid(field.NewPath("status").Child("collisionCount"), value, "cannot be decremented")) 245 } 246 return allErrs 247 } 248 249 // ValidateControllerRevisionName can be used to check whether the given ControllerRevision name is valid. 250 // Prefix indicates this name will be used as part of generation, in which case 251 // trailing dashes are allowed. 252 var ValidateControllerRevisionName = apimachineryvalidation.NameIsDNSSubdomain 253 254 // ValidateControllerRevision collects errors for the fields of state and returns those errors as an ErrorList. If the 255 // returned list is empty, state is valid. Validation is performed to ensure that state is a valid ObjectMeta, its name 256 // is valid, and that it doesn't exceed the MaxControllerRevisionSize. 257 func ValidateControllerRevision(revision *apps.ControllerRevision) field.ErrorList { 258 errs := field.ErrorList{} 259 260 errs = append(errs, apivalidation.ValidateObjectMeta(&revision.ObjectMeta, true, ValidateControllerRevisionName, field.NewPath("metadata"))...) 261 if revision.Data == nil { 262 errs = append(errs, field.Required(field.NewPath("data"), "data is mandatory")) 263 } 264 errs = append(errs, apivalidation.ValidateNonnegativeField(revision.Revision, field.NewPath("revision"))...) 265 return errs 266 } 267 268 // ValidateControllerRevisionUpdate collects errors pertaining to the mutation of an ControllerRevision Object. If the 269 // returned ErrorList is empty the update operation is valid. Any mutation to the ControllerRevision's Data or Revision 270 // is considered to be invalid. 271 func ValidateControllerRevisionUpdate(newHistory, oldHistory *apps.ControllerRevision) field.ErrorList { 272 errs := field.ErrorList{} 273 274 errs = append(errs, apivalidation.ValidateObjectMetaUpdate(&newHistory.ObjectMeta, &oldHistory.ObjectMeta, field.NewPath("metadata"))...) 275 errs = append(errs, ValidateControllerRevision(newHistory)...) 276 errs = append(errs, apivalidation.ValidateImmutableField(newHistory.Data, oldHistory.Data, field.NewPath("data"))...) 277 return errs 278 } 279 280 // ValidateDaemonSet tests if required fields in the DaemonSet are set. 281 func ValidateDaemonSet(ds *apps.DaemonSet, opts apivalidation.PodValidationOptions) field.ErrorList { 282 allErrs := apivalidation.ValidateObjectMeta(&ds.ObjectMeta, true, ValidateDaemonSetName, field.NewPath("metadata")) 283 allErrs = append(allErrs, ValidateDaemonSetSpec(&ds.Spec, field.NewPath("spec"), opts)...) 284 return allErrs 285 } 286 287 // ValidateDaemonSetUpdate tests if required fields in the DaemonSet are set. 288 func ValidateDaemonSetUpdate(ds, oldDS *apps.DaemonSet, opts apivalidation.PodValidationOptions) field.ErrorList { 289 allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata")) 290 allErrs = append(allErrs, ValidateDaemonSetSpecUpdate(&ds.Spec, &oldDS.Spec, field.NewPath("spec"))...) 291 allErrs = append(allErrs, ValidateDaemonSetSpec(&ds.Spec, field.NewPath("spec"), opts)...) 292 return allErrs 293 } 294 295 // ValidateDaemonSetSpecUpdate tests if an update to a DaemonSetSpec is valid. 296 func ValidateDaemonSetSpecUpdate(newSpec, oldSpec *apps.DaemonSetSpec, fldPath *field.Path) field.ErrorList { 297 allErrs := field.ErrorList{} 298 299 // TemplateGeneration shouldn't be decremented 300 if newSpec.TemplateGeneration < oldSpec.TemplateGeneration { 301 allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must not be decremented")) 302 } 303 304 // TemplateGeneration should be increased when and only when template is changed 305 templateUpdated := !apiequality.Semantic.DeepEqual(newSpec.Template, oldSpec.Template) 306 if newSpec.TemplateGeneration == oldSpec.TemplateGeneration && templateUpdated { 307 allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must be incremented upon template update")) 308 } else if newSpec.TemplateGeneration > oldSpec.TemplateGeneration && !templateUpdated { 309 allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must not be incremented without template update")) 310 } 311 312 return allErrs 313 } 314 315 // validateDaemonSetStatus validates a DaemonSetStatus 316 func validateDaemonSetStatus(status *apps.DaemonSetStatus, fldPath *field.Path) field.ErrorList { 317 allErrs := field.ErrorList{} 318 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentNumberScheduled), fldPath.Child("currentNumberScheduled"))...) 319 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberMisscheduled), fldPath.Child("numberMisscheduled"))...) 320 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredNumberScheduled), fldPath.Child("desiredNumberScheduled"))...) 321 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberReady), fldPath.Child("numberReady"))...) 322 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...) 323 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedNumberScheduled), fldPath.Child("updatedNumberScheduled"))...) 324 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberAvailable), fldPath.Child("numberAvailable"))...) 325 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberUnavailable), fldPath.Child("numberUnavailable"))...) 326 if status.CollisionCount != nil { 327 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...) 328 } 329 return allErrs 330 } 331 332 // ValidateDaemonSetStatusUpdate tests if required fields in the DaemonSet Status section 333 func ValidateDaemonSetStatusUpdate(ds, oldDS *apps.DaemonSet) field.ErrorList { 334 allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata")) 335 allErrs = append(allErrs, validateDaemonSetStatus(&ds.Status, field.NewPath("status"))...) 336 if apivalidation.IsDecremented(ds.Status.CollisionCount, oldDS.Status.CollisionCount) { 337 value := int32(0) 338 if ds.Status.CollisionCount != nil { 339 value = *ds.Status.CollisionCount 340 } 341 allErrs = append(allErrs, field.Invalid(field.NewPath("status").Child("collisionCount"), value, "cannot be decremented")) 342 } 343 return allErrs 344 } 345 346 // ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set. 347 func ValidateDaemonSetSpec(spec *apps.DaemonSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 348 allErrs := field.ErrorList{} 349 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector} 350 351 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...) 352 353 selector, err := metav1.LabelSelectorAsSelector(spec.Selector) 354 if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) { 355 allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) 356 } 357 if spec.Selector != nil && len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { 358 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for daemonset")) 359 } 360 361 allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"), opts)...) 362 // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). 363 if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { 364 allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) 365 } 366 if spec.Template.Spec.ActiveDeadlineSeconds != nil { 367 allErrs = append(allErrs, field.Forbidden(fldPath.Child("template", "spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in DaemonSet is not Supported")) 368 } 369 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 370 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.TemplateGeneration), fldPath.Child("templateGeneration"))...) 371 372 allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...) 373 if spec.RevisionHistoryLimit != nil { 374 // zero is a valid RevisionHistoryLimit 375 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...) 376 } 377 return allErrs 378 } 379 380 // ValidateRollingUpdateDaemonSet validates a given RollingUpdateDaemonSet. 381 func ValidateRollingUpdateDaemonSet(rollingUpdate *apps.RollingUpdateDaemonSet, fldPath *field.Path) field.ErrorList { 382 var allErrs field.ErrorList 383 384 // Validate both fields are positive ints or have a percentage value 385 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 386 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) 387 388 // Validate that MaxUnavailable and MaxSurge are not more than 100%. 389 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 390 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) 391 392 // Validate exactly one of MaxSurge or MaxUnavailable is non-zero 393 hasUnavailable := getIntOrPercentValue(rollingUpdate.MaxUnavailable) != 0 394 hasSurge := getIntOrPercentValue(rollingUpdate.MaxSurge) != 0 395 switch { 396 case hasUnavailable && hasSurge: 397 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), rollingUpdate.MaxSurge, "may not be set when maxUnavailable is non-zero")) 398 case !hasUnavailable && !hasSurge: 399 allErrs = append(allErrs, field.Required(fldPath.Child("maxUnavailable"), "cannot be 0 when maxSurge is 0")) 400 } 401 402 return allErrs 403 } 404 405 // validateRollingUpdateStatefulSet validates a given RollingUpdateStatefulSet. 406 func validateRollingUpdateStatefulSet(rollingUpdate *apps.RollingUpdateStatefulSetStrategy, fldPath *field.Path) field.ErrorList { 407 var allErrs field.ErrorList 408 fldPathMaxUn := fldPath.Child("maxUnavailable") 409 allErrs = append(allErrs, 410 apivalidation.ValidateNonnegativeField( 411 int64(rollingUpdate.Partition), 412 fldPath.Child("partition"))...) 413 if rollingUpdate.MaxUnavailable != nil { 414 allErrs = append(allErrs, ValidatePositiveIntOrPercent(*rollingUpdate.MaxUnavailable, fldPathMaxUn)...) 415 if getIntOrPercentValue(*rollingUpdate.MaxUnavailable) == 0 { 416 // MaxUnavailable cannot be 0. 417 allErrs = append(allErrs, field.Invalid(fldPathMaxUn, *rollingUpdate.MaxUnavailable, "cannot be 0")) 418 } 419 // Validate that MaxUnavailable is not more than 100%. 420 allErrs = append(allErrs, IsNotMoreThan100Percent(*rollingUpdate.MaxUnavailable, fldPathMaxUn)...) 421 } 422 return allErrs 423 } 424 425 // ValidateDaemonSetUpdateStrategy validates a given DaemonSetUpdateStrategy. 426 func ValidateDaemonSetUpdateStrategy(strategy *apps.DaemonSetUpdateStrategy, fldPath *field.Path) field.ErrorList { 427 allErrs := field.ErrorList{} 428 switch strategy.Type { 429 case apps.OnDeleteDaemonSetStrategyType: 430 case apps.RollingUpdateDaemonSetStrategyType: 431 // Make sure RollingUpdate field isn't nil. 432 if strategy.RollingUpdate == nil { 433 allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "")) 434 return allErrs 435 } 436 allErrs = append(allErrs, ValidateRollingUpdateDaemonSet(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...) 437 default: 438 validValues := []string{string(apps.RollingUpdateDaemonSetStrategyType), string(apps.OnDeleteDaemonSetStrategyType)} 439 allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues)) 440 } 441 return allErrs 442 } 443 444 // ValidateDaemonSetName can be used to check whether the given daemon set name is valid. 445 // Prefix indicates this name will be used as part of generation, in which case 446 // trailing dashes are allowed. 447 var ValidateDaemonSetName = apimachineryvalidation.NameIsDNSSubdomain 448 449 // ValidateDeploymentName validates that the given name can be used as a deployment name. 450 var ValidateDeploymentName = apimachineryvalidation.NameIsDNSSubdomain 451 452 // ValidatePositiveIntOrPercent tests if a given value is a valid int or 453 // percentage. 454 func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList { 455 allErrs := field.ErrorList{} 456 switch intOrPercent.Type { 457 case intstr.String: 458 for _, msg := range validation.IsValidPercent(intOrPercent.StrVal) { 459 allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, msg)) 460 } 461 case intstr.Int: 462 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...) 463 default: 464 allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')")) 465 } 466 return allErrs 467 } 468 469 func getPercentValue(intOrStringValue intstr.IntOrString) (int, bool) { 470 if intOrStringValue.Type != intstr.String { 471 return 0, false 472 } 473 if len(validation.IsValidPercent(intOrStringValue.StrVal)) != 0 { 474 return 0, false 475 } 476 value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1]) 477 return value, true 478 } 479 480 func getIntOrPercentValue(intOrStringValue intstr.IntOrString) int { 481 value, isPercent := getPercentValue(intOrStringValue) 482 if isPercent { 483 return value 484 } 485 return intOrStringValue.IntValue() 486 } 487 488 // IsNotMoreThan100Percent tests is a value can be represented as a percentage 489 // and if this value is not more than 100%. 490 func IsNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fldPath *field.Path) field.ErrorList { 491 allErrs := field.ErrorList{} 492 value, isPercent := getPercentValue(intOrStringValue) 493 if !isPercent || value <= 100 { 494 return nil 495 } 496 allErrs = append(allErrs, field.Invalid(fldPath, intOrStringValue, "must not be greater than 100%")) 497 return allErrs 498 } 499 500 // ValidateRollingUpdateDeployment validates a given RollingUpdateDeployment. 501 func ValidateRollingUpdateDeployment(rollingUpdate *apps.RollingUpdateDeployment, fldPath *field.Path) field.ErrorList { 502 allErrs := field.ErrorList{} 503 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 504 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) 505 if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 { 506 // Both MaxSurge and MaxUnavailable cannot be zero. 507 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "may not be 0 when `maxSurge` is 0")) 508 } 509 // Validate that MaxUnavailable is not more than 100%. 510 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 511 return allErrs 512 } 513 514 // ValidateDeploymentStrategy validates given DeploymentStrategy. 515 func ValidateDeploymentStrategy(strategy *apps.DeploymentStrategy, fldPath *field.Path) field.ErrorList { 516 allErrs := field.ErrorList{} 517 switch strategy.Type { 518 case apps.RecreateDeploymentStrategyType: 519 if strategy.RollingUpdate != nil { 520 allErrs = append(allErrs, field.Forbidden(fldPath.Child("rollingUpdate"), "may not be specified when strategy `type` is '"+string(apps.RecreateDeploymentStrategyType+"'"))) 521 } 522 case apps.RollingUpdateDeploymentStrategyType: 523 // This should never happen since it's set and checked in defaults.go 524 if strategy.RollingUpdate == nil { 525 allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "this should be defaulted and never be nil")) 526 } else { 527 allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...) 528 } 529 default: 530 validValues := []string{string(apps.RecreateDeploymentStrategyType), string(apps.RollingUpdateDeploymentStrategyType)} 531 allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues)) 532 } 533 return allErrs 534 } 535 536 // ValidateRollback validates given RollbackConfig. 537 func ValidateRollback(rollback *apps.RollbackConfig, fldPath *field.Path) field.ErrorList { 538 allErrs := field.ErrorList{} 539 v := rollback.Revision 540 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(v), fldPath.Child("version"))...) 541 return allErrs 542 } 543 544 // ValidateDeploymentSpec validates given deployment spec. 545 func ValidateDeploymentSpec(spec, oldSpec *apps.DeploymentSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 546 allErrs := field.ErrorList{} 547 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) 548 549 if spec.Selector == nil { 550 allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) 551 } else { 552 // validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below 553 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...) 554 if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { 555 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment")) 556 } 557 } 558 559 selector, err := metav1.LabelSelectorAsSelector(spec.Selector) 560 if err != nil { 561 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector")) 562 } else { 563 allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"), opts)...) 564 } 565 566 allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, fldPath.Child("strategy"))...) 567 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 568 if spec.RevisionHistoryLimit != nil { 569 // zero is a valid RevisionHistoryLimit 570 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...) 571 } 572 if spec.RollbackTo != nil { 573 allErrs = append(allErrs, ValidateRollback(spec.RollbackTo, fldPath.Child("rollback"))...) 574 } 575 if spec.ProgressDeadlineSeconds != nil { 576 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ProgressDeadlineSeconds), fldPath.Child("progressDeadlineSeconds"))...) 577 if *spec.ProgressDeadlineSeconds <= spec.MinReadySeconds { 578 allErrs = append(allErrs, field.Invalid(fldPath.Child("progressDeadlineSeconds"), spec.ProgressDeadlineSeconds, "must be greater than minReadySeconds")) 579 } 580 } 581 return allErrs 582 } 583 584 // ValidateDeploymentStatus validates given deployment status. 585 func ValidateDeploymentStatus(status *apps.DeploymentStatus, fldPath *field.Path) field.ErrorList { 586 allErrs := field.ErrorList{} 587 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...) 588 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...) 589 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fldPath.Child("updatedReplicas"))...) 590 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...) 591 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...) 592 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...) 593 if status.CollisionCount != nil { 594 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...) 595 } 596 msg := "cannot be greater than status.replicas" 597 if status.UpdatedReplicas > status.Replicas { 598 allErrs = append(allErrs, field.Invalid(fldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg)) 599 } 600 if status.ReadyReplicas > status.Replicas { 601 allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg)) 602 } 603 if status.AvailableReplicas > status.Replicas { 604 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg)) 605 } 606 if status.AvailableReplicas > status.ReadyReplicas { 607 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) 608 } 609 return allErrs 610 } 611 612 // ValidateDeploymentUpdate tests if an update to a Deployment is valid. 613 func ValidateDeploymentUpdate(update, old *apps.Deployment, opts apivalidation.PodValidationOptions) field.ErrorList { 614 allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 615 allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec, &old.Spec, field.NewPath("spec"), opts)...) 616 return allErrs 617 } 618 619 // ValidateDeploymentStatusUpdate tests if a an update to a Deployment status 620 // is valid. 621 func ValidateDeploymentStatusUpdate(update, old *apps.Deployment) field.ErrorList { 622 allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 623 fldPath := field.NewPath("status") 624 allErrs = append(allErrs, ValidateDeploymentStatus(&update.Status, fldPath)...) 625 if apivalidation.IsDecremented(update.Status.CollisionCount, old.Status.CollisionCount) { 626 value := int32(0) 627 if update.Status.CollisionCount != nil { 628 value = *update.Status.CollisionCount 629 } 630 allErrs = append(allErrs, field.Invalid(fldPath.Child("collisionCount"), value, "cannot be decremented")) 631 } 632 return allErrs 633 } 634 635 // ValidateDeployment validates a given Deployment. 636 func ValidateDeployment(obj *apps.Deployment, opts apivalidation.PodValidationOptions) field.ErrorList { 637 allErrs := apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata")) 638 allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec, nil, field.NewPath("spec"), opts)...) 639 return allErrs 640 } 641 642 // ValidateDeploymentRollback validates a given DeploymentRollback. 643 func ValidateDeploymentRollback(obj *apps.DeploymentRollback) field.ErrorList { 644 allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations")) 645 if len(obj.Name) == 0 { 646 allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required")) 647 } 648 allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...) 649 return allErrs 650 } 651 652 // ValidateReplicaSetName can be used to check whether the given ReplicaSet 653 // name is valid. 654 // Prefix indicates this name will be used as part of generation, in which case 655 // trailing dashes are allowed. 656 var ValidateReplicaSetName = apimachineryvalidation.NameIsDNSSubdomain 657 658 // ValidateReplicaSet tests if required fields in the ReplicaSet are set. 659 func ValidateReplicaSet(rs *apps.ReplicaSet, opts apivalidation.PodValidationOptions) field.ErrorList { 660 allErrs := apivalidation.ValidateObjectMeta(&rs.ObjectMeta, true, ValidateReplicaSetName, field.NewPath("metadata")) 661 allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, nil, field.NewPath("spec"), opts)...) 662 return allErrs 663 } 664 665 // ValidateReplicaSetUpdate tests if required fields in the ReplicaSet are set. 666 func ValidateReplicaSetUpdate(rs, oldRs *apps.ReplicaSet, opts apivalidation.PodValidationOptions) field.ErrorList { 667 allErrs := field.ErrorList{} 668 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...) 669 allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, &oldRs.Spec, field.NewPath("spec"), opts)...) 670 return allErrs 671 } 672 673 // ValidateReplicaSetStatusUpdate tests if required fields in the ReplicaSet are set. 674 func ValidateReplicaSetStatusUpdate(rs, oldRs *apps.ReplicaSet) field.ErrorList { 675 allErrs := field.ErrorList{} 676 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...) 677 allErrs = append(allErrs, ValidateReplicaSetStatus(rs.Status, field.NewPath("status"))...) 678 return allErrs 679 } 680 681 // ValidateReplicaSetStatus validates a given ReplicaSetStatus. 682 func ValidateReplicaSetStatus(status apps.ReplicaSetStatus, fldPath *field.Path) field.ErrorList { 683 allErrs := field.ErrorList{} 684 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...) 685 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.FullyLabeledReplicas), fldPath.Child("fullyLabeledReplicas"))...) 686 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...) 687 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...) 688 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ObservedGeneration), fldPath.Child("observedGeneration"))...) 689 msg := "cannot be greater than status.replicas" 690 if status.FullyLabeledReplicas > status.Replicas { 691 allErrs = append(allErrs, field.Invalid(fldPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg)) 692 } 693 if status.ReadyReplicas > status.Replicas { 694 allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg)) 695 } 696 if status.AvailableReplicas > status.Replicas { 697 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg)) 698 } 699 if status.AvailableReplicas > status.ReadyReplicas { 700 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) 701 } 702 return allErrs 703 } 704 705 // ValidateReplicaSetSpec tests if required fields in the ReplicaSet spec are set. 706 func ValidateReplicaSetSpec(spec, oldSpec *apps.ReplicaSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 707 allErrs := field.ErrorList{} 708 709 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) 710 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 711 712 if spec.Selector == nil { 713 allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) 714 } else { 715 // validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below 716 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...) 717 if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { 718 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment")) 719 } 720 } 721 722 selector, err := metav1.LabelSelectorAsSelector(spec.Selector) 723 if err != nil { 724 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector")) 725 } else { 726 allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"), opts)...) 727 } 728 return allErrs 729 } 730 731 // ValidatePodTemplateSpecForReplicaSet validates the given template and ensures that it is in accordance with the desired selector and replicas. 732 func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selector labels.Selector, replicas int32, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 733 allErrs := field.ErrorList{} 734 if template == nil { 735 allErrs = append(allErrs, field.Required(fldPath, "")) 736 } else { 737 if !selector.Empty() { 738 // Verify that the ReplicaSet selector matches the labels in template. 739 labels := labels.Set(template.Labels) 740 if !selector.Matches(labels) { 741 allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) 742 } 743 } 744 allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath, opts)...) 745 // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). 746 if template.Spec.RestartPolicy != api.RestartPolicyAlways { 747 allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) 748 } 749 if template.Spec.ActiveDeadlineSeconds != nil { 750 allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in ReplicaSet is not Supported")) 751 } 752 } 753 return allErrs 754 }