k8s.io/kubernetes@v1.29.3/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, nil, 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, &oldDS.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, oldSpec *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 // get rid of apivalidation.ValidateReadOnlyPersistentDisks,stop passing oldSpec to this function 363 var oldVols []api.Volume 364 if oldSpec != nil { 365 oldVols = oldSpec.Template.Spec.Volumes // +k8s:verify-mutation:reason=clone 366 } 367 allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes, oldVols, fldPath.Child("template", "spec", "volumes"))...) 368 // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). 369 if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { 370 allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) 371 } 372 if spec.Template.Spec.ActiveDeadlineSeconds != nil { 373 allErrs = append(allErrs, field.Forbidden(fldPath.Child("template", "spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in DaemonSet is not Supported")) 374 } 375 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 376 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.TemplateGeneration), fldPath.Child("templateGeneration"))...) 377 378 allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...) 379 if spec.RevisionHistoryLimit != nil { 380 // zero is a valid RevisionHistoryLimit 381 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...) 382 } 383 return allErrs 384 } 385 386 // ValidateRollingUpdateDaemonSet validates a given RollingUpdateDaemonSet. 387 func ValidateRollingUpdateDaemonSet(rollingUpdate *apps.RollingUpdateDaemonSet, fldPath *field.Path) field.ErrorList { 388 var allErrs field.ErrorList 389 390 // Validate both fields are positive ints or have a percentage value 391 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 392 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) 393 394 // Validate that MaxUnavailable and MaxSurge are not more than 100%. 395 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 396 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) 397 398 // Validate exactly one of MaxSurge or MaxUnavailable is non-zero 399 hasUnavailable := getIntOrPercentValue(rollingUpdate.MaxUnavailable) != 0 400 hasSurge := getIntOrPercentValue(rollingUpdate.MaxSurge) != 0 401 switch { 402 case hasUnavailable && hasSurge: 403 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), rollingUpdate.MaxSurge, "may not be set when maxUnavailable is non-zero")) 404 case !hasUnavailable && !hasSurge: 405 allErrs = append(allErrs, field.Required(fldPath.Child("maxUnavailable"), "cannot be 0 when maxSurge is 0")) 406 } 407 408 return allErrs 409 } 410 411 // validateRollingUpdateStatefulSet validates a given RollingUpdateStatefulSet. 412 func validateRollingUpdateStatefulSet(rollingUpdate *apps.RollingUpdateStatefulSetStrategy, fldPath *field.Path) field.ErrorList { 413 var allErrs field.ErrorList 414 fldPathMaxUn := fldPath.Child("maxUnavailable") 415 allErrs = append(allErrs, 416 apivalidation.ValidateNonnegativeField( 417 int64(rollingUpdate.Partition), 418 fldPath.Child("partition"))...) 419 if rollingUpdate.MaxUnavailable != nil { 420 allErrs = append(allErrs, ValidatePositiveIntOrPercent(*rollingUpdate.MaxUnavailable, fldPathMaxUn)...) 421 if getIntOrPercentValue(*rollingUpdate.MaxUnavailable) == 0 { 422 // MaxUnavailable cannot be 0. 423 allErrs = append(allErrs, field.Invalid(fldPathMaxUn, *rollingUpdate.MaxUnavailable, "cannot be 0")) 424 } 425 // Validate that MaxUnavailable is not more than 100%. 426 allErrs = append(allErrs, IsNotMoreThan100Percent(*rollingUpdate.MaxUnavailable, fldPathMaxUn)...) 427 } 428 return allErrs 429 } 430 431 // ValidateDaemonSetUpdateStrategy validates a given DaemonSetUpdateStrategy. 432 func ValidateDaemonSetUpdateStrategy(strategy *apps.DaemonSetUpdateStrategy, fldPath *field.Path) field.ErrorList { 433 allErrs := field.ErrorList{} 434 switch strategy.Type { 435 case apps.OnDeleteDaemonSetStrategyType: 436 case apps.RollingUpdateDaemonSetStrategyType: 437 // Make sure RollingUpdate field isn't nil. 438 if strategy.RollingUpdate == nil { 439 allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "")) 440 return allErrs 441 } 442 allErrs = append(allErrs, ValidateRollingUpdateDaemonSet(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...) 443 default: 444 validValues := []string{string(apps.RollingUpdateDaemonSetStrategyType), string(apps.OnDeleteDaemonSetStrategyType)} 445 allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues)) 446 } 447 return allErrs 448 } 449 450 // ValidateDaemonSetName can be used to check whether the given daemon set name is valid. 451 // Prefix indicates this name will be used as part of generation, in which case 452 // trailing dashes are allowed. 453 var ValidateDaemonSetName = apimachineryvalidation.NameIsDNSSubdomain 454 455 // ValidateDeploymentName validates that the given name can be used as a deployment name. 456 var ValidateDeploymentName = apimachineryvalidation.NameIsDNSSubdomain 457 458 // ValidatePositiveIntOrPercent tests if a given value is a valid int or 459 // percentage. 460 func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList { 461 allErrs := field.ErrorList{} 462 switch intOrPercent.Type { 463 case intstr.String: 464 for _, msg := range validation.IsValidPercent(intOrPercent.StrVal) { 465 allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, msg)) 466 } 467 case intstr.Int: 468 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...) 469 default: 470 allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')")) 471 } 472 return allErrs 473 } 474 475 func getPercentValue(intOrStringValue intstr.IntOrString) (int, bool) { 476 if intOrStringValue.Type != intstr.String { 477 return 0, false 478 } 479 if len(validation.IsValidPercent(intOrStringValue.StrVal)) != 0 { 480 return 0, false 481 } 482 value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1]) 483 return value, true 484 } 485 486 func getIntOrPercentValue(intOrStringValue intstr.IntOrString) int { 487 value, isPercent := getPercentValue(intOrStringValue) 488 if isPercent { 489 return value 490 } 491 return intOrStringValue.IntValue() 492 } 493 494 // IsNotMoreThan100Percent tests is a value can be represented as a percentage 495 // and if this value is not more than 100%. 496 func IsNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fldPath *field.Path) field.ErrorList { 497 allErrs := field.ErrorList{} 498 value, isPercent := getPercentValue(intOrStringValue) 499 if !isPercent || value <= 100 { 500 return nil 501 } 502 allErrs = append(allErrs, field.Invalid(fldPath, intOrStringValue, "must not be greater than 100%")) 503 return allErrs 504 } 505 506 // ValidateRollingUpdateDeployment validates a given RollingUpdateDeployment. 507 func ValidateRollingUpdateDeployment(rollingUpdate *apps.RollingUpdateDeployment, fldPath *field.Path) field.ErrorList { 508 allErrs := field.ErrorList{} 509 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 510 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) 511 if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 { 512 // Both MaxSurge and MaxUnavailable cannot be zero. 513 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "may not be 0 when `maxSurge` is 0")) 514 } 515 // Validate that MaxUnavailable is not more than 100%. 516 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...) 517 return allErrs 518 } 519 520 // ValidateDeploymentStrategy validates given DeploymentStrategy. 521 func ValidateDeploymentStrategy(strategy *apps.DeploymentStrategy, fldPath *field.Path) field.ErrorList { 522 allErrs := field.ErrorList{} 523 switch strategy.Type { 524 case apps.RecreateDeploymentStrategyType: 525 if strategy.RollingUpdate != nil { 526 allErrs = append(allErrs, field.Forbidden(fldPath.Child("rollingUpdate"), "may not be specified when strategy `type` is '"+string(apps.RecreateDeploymentStrategyType+"'"))) 527 } 528 case apps.RollingUpdateDeploymentStrategyType: 529 // This should never happen since it's set and checked in defaults.go 530 if strategy.RollingUpdate == nil { 531 allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "this should be defaulted and never be nil")) 532 } else { 533 allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...) 534 } 535 default: 536 validValues := []string{string(apps.RecreateDeploymentStrategyType), string(apps.RollingUpdateDeploymentStrategyType)} 537 allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues)) 538 } 539 return allErrs 540 } 541 542 // ValidateRollback validates given RollbackConfig. 543 func ValidateRollback(rollback *apps.RollbackConfig, fldPath *field.Path) field.ErrorList { 544 allErrs := field.ErrorList{} 545 v := rollback.Revision 546 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(v), fldPath.Child("version"))...) 547 return allErrs 548 } 549 550 // ValidateDeploymentSpec validates given deployment spec. 551 func ValidateDeploymentSpec(spec, oldSpec *apps.DeploymentSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 552 allErrs := field.ErrorList{} 553 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) 554 555 if spec.Selector == nil { 556 allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) 557 } else { 558 // validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below 559 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...) 560 if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { 561 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment")) 562 } 563 } 564 565 selector, err := metav1.LabelSelectorAsSelector(spec.Selector) 566 if err != nil { 567 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector")) 568 } else { 569 // oldSpec is not empty, pass oldSpec.template 570 var oldTemplate *api.PodTemplateSpec 571 if oldSpec != nil { 572 oldTemplate = &oldSpec.Template // +k8s:verify-mutation:reason=clone 573 } 574 allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, oldTemplate, selector, spec.Replicas, fldPath.Child("template"), opts)...) 575 } 576 577 allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, fldPath.Child("strategy"))...) 578 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 579 if spec.RevisionHistoryLimit != nil { 580 // zero is a valid RevisionHistoryLimit 581 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...) 582 } 583 if spec.RollbackTo != nil { 584 allErrs = append(allErrs, ValidateRollback(spec.RollbackTo, fldPath.Child("rollback"))...) 585 } 586 if spec.ProgressDeadlineSeconds != nil { 587 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ProgressDeadlineSeconds), fldPath.Child("progressDeadlineSeconds"))...) 588 if *spec.ProgressDeadlineSeconds <= spec.MinReadySeconds { 589 allErrs = append(allErrs, field.Invalid(fldPath.Child("progressDeadlineSeconds"), spec.ProgressDeadlineSeconds, "must be greater than minReadySeconds")) 590 } 591 } 592 return allErrs 593 } 594 595 // ValidateDeploymentStatus validates given deployment status. 596 func ValidateDeploymentStatus(status *apps.DeploymentStatus, fldPath *field.Path) field.ErrorList { 597 allErrs := field.ErrorList{} 598 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...) 599 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...) 600 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fldPath.Child("updatedReplicas"))...) 601 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...) 602 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...) 603 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...) 604 if status.CollisionCount != nil { 605 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...) 606 } 607 msg := "cannot be greater than status.replicas" 608 if status.UpdatedReplicas > status.Replicas { 609 allErrs = append(allErrs, field.Invalid(fldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg)) 610 } 611 if status.ReadyReplicas > status.Replicas { 612 allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg)) 613 } 614 if status.AvailableReplicas > status.Replicas { 615 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg)) 616 } 617 if status.AvailableReplicas > status.ReadyReplicas { 618 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) 619 } 620 return allErrs 621 } 622 623 // ValidateDeploymentUpdate tests if an update to a Deployment is valid. 624 func ValidateDeploymentUpdate(update, old *apps.Deployment, opts apivalidation.PodValidationOptions) field.ErrorList { 625 allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 626 allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec, &old.Spec, field.NewPath("spec"), opts)...) 627 return allErrs 628 } 629 630 // ValidateDeploymentStatusUpdate tests if a an update to a Deployment status 631 // is valid. 632 func ValidateDeploymentStatusUpdate(update, old *apps.Deployment) field.ErrorList { 633 allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 634 fldPath := field.NewPath("status") 635 allErrs = append(allErrs, ValidateDeploymentStatus(&update.Status, fldPath)...) 636 if apivalidation.IsDecremented(update.Status.CollisionCount, old.Status.CollisionCount) { 637 value := int32(0) 638 if update.Status.CollisionCount != nil { 639 value = *update.Status.CollisionCount 640 } 641 allErrs = append(allErrs, field.Invalid(fldPath.Child("collisionCount"), value, "cannot be decremented")) 642 } 643 return allErrs 644 } 645 646 // ValidateDeployment validates a given Deployment. 647 func ValidateDeployment(obj *apps.Deployment, opts apivalidation.PodValidationOptions) field.ErrorList { 648 allErrs := apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata")) 649 allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec, nil, field.NewPath("spec"), opts)...) 650 return allErrs 651 } 652 653 // ValidateDeploymentRollback validates a given DeploymentRollback. 654 func ValidateDeploymentRollback(obj *apps.DeploymentRollback) field.ErrorList { 655 allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations")) 656 if len(obj.Name) == 0 { 657 allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required")) 658 } 659 allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...) 660 return allErrs 661 } 662 663 // ValidateReplicaSetName can be used to check whether the given ReplicaSet 664 // name is valid. 665 // Prefix indicates this name will be used as part of generation, in which case 666 // trailing dashes are allowed. 667 var ValidateReplicaSetName = apimachineryvalidation.NameIsDNSSubdomain 668 669 // ValidateReplicaSet tests if required fields in the ReplicaSet are set. 670 func ValidateReplicaSet(rs *apps.ReplicaSet, opts apivalidation.PodValidationOptions) field.ErrorList { 671 allErrs := apivalidation.ValidateObjectMeta(&rs.ObjectMeta, true, ValidateReplicaSetName, field.NewPath("metadata")) 672 allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, nil, field.NewPath("spec"), opts)...) 673 return allErrs 674 } 675 676 // ValidateReplicaSetUpdate tests if required fields in the ReplicaSet are set. 677 func ValidateReplicaSetUpdate(rs, oldRs *apps.ReplicaSet, opts apivalidation.PodValidationOptions) field.ErrorList { 678 allErrs := field.ErrorList{} 679 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...) 680 allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, &oldRs.Spec, field.NewPath("spec"), opts)...) 681 return allErrs 682 } 683 684 // ValidateReplicaSetStatusUpdate tests if required fields in the ReplicaSet are set. 685 func ValidateReplicaSetStatusUpdate(rs, oldRs *apps.ReplicaSet) field.ErrorList { 686 allErrs := field.ErrorList{} 687 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...) 688 allErrs = append(allErrs, ValidateReplicaSetStatus(rs.Status, field.NewPath("status"))...) 689 return allErrs 690 } 691 692 // ValidateReplicaSetStatus validates a given ReplicaSetStatus. 693 func ValidateReplicaSetStatus(status apps.ReplicaSetStatus, fldPath *field.Path) field.ErrorList { 694 allErrs := field.ErrorList{} 695 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...) 696 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.FullyLabeledReplicas), fldPath.Child("fullyLabeledReplicas"))...) 697 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...) 698 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...) 699 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ObservedGeneration), fldPath.Child("observedGeneration"))...) 700 msg := "cannot be greater than status.replicas" 701 if status.FullyLabeledReplicas > status.Replicas { 702 allErrs = append(allErrs, field.Invalid(fldPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg)) 703 } 704 if status.ReadyReplicas > status.Replicas { 705 allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg)) 706 } 707 if status.AvailableReplicas > status.Replicas { 708 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg)) 709 } 710 if status.AvailableReplicas > status.ReadyReplicas { 711 allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) 712 } 713 return allErrs 714 } 715 716 // ValidateReplicaSetSpec tests if required fields in the ReplicaSet spec are set. 717 func ValidateReplicaSetSpec(spec, oldSpec *apps.ReplicaSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 718 allErrs := field.ErrorList{} 719 720 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) 721 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 722 723 if spec.Selector == nil { 724 allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) 725 } else { 726 // validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below 727 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...) 728 if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { 729 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment")) 730 } 731 } 732 733 selector, err := metav1.LabelSelectorAsSelector(spec.Selector) 734 if err != nil { 735 allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector")) 736 } else { 737 // oldSpec is not empty, pass oldSpec.template. 738 var oldTemplate *api.PodTemplateSpec 739 if oldSpec != nil { 740 oldTemplate = &oldSpec.Template // +k8s:verify-mutation:reason=clone 741 } 742 allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, oldTemplate, selector, spec.Replicas, fldPath.Child("template"), opts)...) 743 } 744 return allErrs 745 } 746 747 // ValidatePodTemplateSpecForReplicaSet validates the given template and ensures that it is in accordance with the desired selector and replicas. 748 func ValidatePodTemplateSpecForReplicaSet(template, oldTemplate *api.PodTemplateSpec, selector labels.Selector, replicas int32, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { 749 allErrs := field.ErrorList{} 750 if template == nil { 751 allErrs = append(allErrs, field.Required(fldPath, "")) 752 } else { 753 if !selector.Empty() { 754 // Verify that the ReplicaSet selector matches the labels in template. 755 labels := labels.Set(template.Labels) 756 if !selector.Matches(labels) { 757 allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) 758 } 759 } 760 allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath, opts)...) 761 // Daemons run on more than one node, Cancel verification of read and write volumes. 762 // get rid of apivalidation.ValidateReadOnlyPersistentDisks,stop passing oldTemplate to this function 763 var oldVols []api.Volume 764 if oldTemplate != nil { 765 oldVols = oldTemplate.Spec.Volumes // +k8s:verify-mutation:reason=clone 766 } 767 if replicas > 1 { 768 allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(template.Spec.Volumes, oldVols, fldPath.Child("spec", "volumes"))...) 769 } 770 // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). 771 if template.Spec.RestartPolicy != api.RestartPolicyAlways { 772 allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) 773 } 774 if template.Spec.ActiveDeadlineSeconds != nil { 775 allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in ReplicaSet is not Supported")) 776 } 777 } 778 return allErrs 779 }