github.com/enmand/kubernetes@v1.2.0-alpha.0/pkg/apis/experimental/validation/validation.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors All rights reserved. 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 "strconv" 21 22 "k8s.io/kubernetes/pkg/api" 23 apivalidation "k8s.io/kubernetes/pkg/api/validation" 24 "k8s.io/kubernetes/pkg/apis/experimental" 25 "k8s.io/kubernetes/pkg/labels" 26 "k8s.io/kubernetes/pkg/util" 27 errs "k8s.io/kubernetes/pkg/util/fielderrors" 28 "k8s.io/kubernetes/pkg/util/sets" 29 "k8s.io/kubernetes/pkg/util/validation" 30 ) 31 32 const isNegativeErrorMsg string = `must be non-negative` 33 34 // ValidateHorizontalPodAutoscaler can be used to check whether the given autoscaler name is valid. 35 // Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed. 36 func ValidateHorizontalPodAutoscalerName(name string, prefix bool) (bool, string) { 37 // TODO: finally move it to pkg/api/validation and use nameIsDNSSubdomain function 38 return apivalidation.ValidateReplicationControllerName(name, prefix) 39 } 40 41 func validateHorizontalPodAutoscalerSpec(autoscaler experimental.HorizontalPodAutoscalerSpec) errs.ValidationErrorList { 42 allErrs := errs.ValidationErrorList{} 43 if autoscaler.MinReplicas < 0 { 44 allErrs = append(allErrs, errs.NewFieldInvalid("minReplicas", autoscaler.MinReplicas, `must be non-negative`)) 45 } 46 if autoscaler.MaxReplicas < autoscaler.MinReplicas { 47 allErrs = append(allErrs, errs.NewFieldInvalid("maxReplicas", autoscaler.MaxReplicas, `must be bigger or equal to minReplicas`)) 48 } 49 if autoscaler.ScaleRef == nil { 50 allErrs = append(allErrs, errs.NewFieldRequired("scaleRef")) 51 } 52 resource := autoscaler.Target.Resource.String() 53 if resource != string(api.ResourceMemory) && resource != string(api.ResourceCPU) { 54 allErrs = append(allErrs, errs.NewFieldInvalid("target.resource", resource, "resource not supported by autoscaler")) 55 } 56 quantity := autoscaler.Target.Quantity.Value() 57 if quantity < 0 { 58 allErrs = append(allErrs, errs.NewFieldInvalid("target.quantity", quantity, "must be non-negative")) 59 } 60 return allErrs 61 } 62 63 func ValidateHorizontalPodAutoscaler(autoscaler *experimental.HorizontalPodAutoscaler) errs.ValidationErrorList { 64 allErrs := errs.ValidationErrorList{} 65 allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName).Prefix("metadata")...) 66 allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec)...) 67 return allErrs 68 } 69 70 func ValidateHorizontalPodAutoscalerUpdate(newAutoscler, oldAutoscaler *experimental.HorizontalPodAutoscaler) errs.ValidationErrorList { 71 allErrs := errs.ValidationErrorList{} 72 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&newAutoscler.ObjectMeta, &oldAutoscaler.ObjectMeta).Prefix("metadata")...) 73 allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscler.Spec)...) 74 return allErrs 75 } 76 77 func ValidateThirdPartyResourceUpdate(old, update *experimental.ThirdPartyResource) errs.ValidationErrorList { 78 return ValidateThirdPartyResource(update) 79 } 80 81 func ValidateThirdPartyResource(obj *experimental.ThirdPartyResource) errs.ValidationErrorList { 82 allErrs := errs.ValidationErrorList{} 83 if len(obj.Name) == 0 { 84 allErrs = append(allErrs, errs.NewFieldInvalid("name", obj.Name, "name must be non-empty")) 85 } 86 versions := sets.String{} 87 for ix := range obj.Versions { 88 version := &obj.Versions[ix] 89 if len(version.Name) == 0 { 90 allErrs = append(allErrs, errs.NewFieldInvalid("name", version, "name can not be empty")) 91 } 92 if versions.Has(version.Name) { 93 allErrs = append(allErrs, errs.NewFieldDuplicate("version", version)) 94 } 95 versions.Insert(version.Name) 96 } 97 return allErrs 98 } 99 100 // ValidateDaemonSet tests if required fields in the DaemonSet are set. 101 func ValidateDaemonSet(controller *experimental.DaemonSet) errs.ValidationErrorList { 102 allErrs := errs.ValidationErrorList{} 103 allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&controller.ObjectMeta, true, apivalidation.ValidateReplicationControllerName).Prefix("metadata")...) 104 allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec).Prefix("spec")...) 105 return allErrs 106 } 107 108 // ValidateDaemonSetUpdate tests if required fields in the DaemonSet are set. 109 func ValidateDaemonSetUpdate(oldController, controller *experimental.DaemonSet) errs.ValidationErrorList { 110 allErrs := errs.ValidationErrorList{} 111 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) 112 allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec).Prefix("spec")...) 113 allErrs = append(allErrs, ValidateDaemonSetTemplateUpdate(oldController.Spec.Template, controller.Spec.Template).Prefix("spec.template")...) 114 return allErrs 115 } 116 117 // validateDaemonSetStatus validates a DaemonSetStatus 118 func validateDaemonSetStatus(status *experimental.DaemonSetStatus) errs.ValidationErrorList { 119 allErrs := errs.ValidationErrorList{} 120 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentNumberScheduled), "currentNumberScheduled")...) 121 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.NumberMisscheduled), "numberMisscheduled")...) 122 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredNumberScheduled), "desiredNumberScheduled")...) 123 return allErrs 124 } 125 126 // ValidateDaemonSetStatus validates tests if required fields in the DaemonSet Status section 127 func ValidateDaemonSetStatusUpdate(controller, oldController *experimental.DaemonSet) errs.ValidationErrorList { 128 allErrs := errs.ValidationErrorList{} 129 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) 130 allErrs = append(allErrs, validateDaemonSetStatus(&controller.Status)...) 131 return allErrs 132 } 133 134 // ValidateDaemonSetTemplateUpdate tests that certain fields in the daemon set's pod template are not updated. 135 func ValidateDaemonSetTemplateUpdate(oldPodTemplate, podTemplate *api.PodTemplateSpec) errs.ValidationErrorList { 136 allErrs := errs.ValidationErrorList{} 137 podSpec := podTemplate.Spec 138 // podTemplate.Spec is not a pointer, so we can modify NodeSelector and NodeName directly. 139 podSpec.NodeSelector = oldPodTemplate.Spec.NodeSelector 140 podSpec.NodeName = oldPodTemplate.Spec.NodeName 141 // In particular, we do not allow updates to container images at this point. 142 if !api.Semantic.DeepEqual(oldPodTemplate.Spec, podSpec) { 143 // TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff 144 allErrs = append(allErrs, errs.NewFieldInvalid("spec", "content of spec is not printed out, please refer to the \"details\"", "may not update fields other than spec.nodeSelector")) 145 } 146 return allErrs 147 } 148 149 // ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set. 150 func ValidateDaemonSetSpec(spec *experimental.DaemonSetSpec) errs.ValidationErrorList { 151 allErrs := errs.ValidationErrorList{} 152 153 selector := labels.Set(spec.Selector).AsSelector() 154 if selector.Empty() { 155 allErrs = append(allErrs, errs.NewFieldRequired("selector")) 156 } 157 158 if spec.Template == nil { 159 allErrs = append(allErrs, errs.NewFieldRequired("template")) 160 } else { 161 labels := labels.Set(spec.Template.Labels) 162 if !selector.Matches(labels) { 163 allErrs = append(allErrs, errs.NewFieldInvalid("template.metadata.labels", spec.Template.Labels, "selector does not match template")) 164 } 165 allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template).Prefix("template")...) 166 // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. 167 allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes).Prefix("template.spec.volumes")...) 168 // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). 169 if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { 170 allErrs = append(allErrs, errs.NewFieldValueNotSupported("template.spec.restartPolicy", spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) 171 } 172 } 173 return allErrs 174 } 175 176 // ValidateDaemonSetName can be used to check whether the given daemon set name is valid. 177 // Prefix indicates this name will be used as part of generation, in which case 178 // trailing dashes are allowed. 179 func ValidateDaemonSetName(name string, prefix bool) (bool, string) { 180 return apivalidation.NameIsDNSSubdomain(name, prefix) 181 } 182 183 // Validates that the given name can be used as a deployment name. 184 func ValidateDeploymentName(name string, prefix bool) (bool, string) { 185 return apivalidation.NameIsDNSSubdomain(name, prefix) 186 } 187 188 func ValidatePositiveIntOrPercent(intOrPercent util.IntOrString, fieldName string) errs.ValidationErrorList { 189 allErrs := errs.ValidationErrorList{} 190 if intOrPercent.Kind == util.IntstrString { 191 if !validation.IsValidPercent(intOrPercent.StrVal) { 192 allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, intOrPercent, "value should be int(5) or percentage(5%)")) 193 } 194 195 } else if intOrPercent.Kind == util.IntstrInt { 196 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(intOrPercent.IntVal), fieldName)...) 197 } 198 return allErrs 199 } 200 201 func getPercentValue(intOrStringValue util.IntOrString) (int, bool) { 202 if intOrStringValue.Kind != util.IntstrString || !validation.IsValidPercent(intOrStringValue.StrVal) { 203 return 0, false 204 } 205 value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1]) 206 return value, true 207 } 208 209 func getIntOrPercentValue(intOrStringValue util.IntOrString) int { 210 value, isPercent := getPercentValue(intOrStringValue) 211 if isPercent { 212 return value 213 } 214 return intOrStringValue.IntVal 215 } 216 217 func IsNotMoreThan100Percent(intOrStringValue util.IntOrString, fieldName string) errs.ValidationErrorList { 218 allErrs := errs.ValidationErrorList{} 219 value, isPercent := getPercentValue(intOrStringValue) 220 if !isPercent || value <= 100 { 221 return nil 222 } 223 allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, intOrStringValue, "should not be more than 100%")) 224 return allErrs 225 } 226 227 func ValidateRollingUpdateDeployment(rollingUpdate *experimental.RollingUpdateDeployment, fieldName string) errs.ValidationErrorList { 228 allErrs := errs.ValidationErrorList{} 229 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fieldName+"maxUnavailable")...) 230 allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fieldName+".maxSurge")...) 231 if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 { 232 // Both MaxSurge and MaxUnavailable cannot be zero. 233 allErrs = append(allErrs, errs.NewFieldInvalid(fieldName+".maxUnavailable", rollingUpdate.MaxUnavailable, "cannot be 0 when maxSurge is 0 as well")) 234 } 235 // Validate that MaxUnavailable is not more than 100%. 236 allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fieldName+".maxUnavailable")...) 237 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(rollingUpdate.MinReadySeconds), fieldName+".minReadySeconds")...) 238 return allErrs 239 } 240 241 func ValidateDeploymentStrategy(strategy *experimental.DeploymentStrategy, fieldName string) errs.ValidationErrorList { 242 allErrs := errs.ValidationErrorList{} 243 if strategy.RollingUpdate == nil { 244 return allErrs 245 } 246 switch strategy.Type { 247 case experimental.RecreateDeploymentStrategyType: 248 allErrs = append(allErrs, errs.NewFieldForbidden("rollingUpdate", "rollingUpdate should be nil when strategy type is "+experimental.RecreateDeploymentStrategyType)) 249 case experimental.RollingUpdateDeploymentStrategyType: 250 allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, "rollingUpdate")...) 251 } 252 return allErrs 253 } 254 255 // Validates given deployment spec. 256 func ValidateDeploymentSpec(spec *experimental.DeploymentSpec) errs.ValidationErrorList { 257 allErrs := errs.ValidationErrorList{} 258 allErrs = append(allErrs, apivalidation.ValidateNonEmptySelector(spec.Selector, "selector")...) 259 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(spec.Replicas), "replicas")...) 260 allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpecForRC(spec.Template, spec.Selector, spec.Replicas, "template")...) 261 allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, "strategy")...) 262 allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, "uniqueLabel")...) 263 return allErrs 264 } 265 266 func ValidateDeploymentUpdate(old, update *experimental.Deployment) errs.ValidationErrorList { 267 allErrs := errs.ValidationErrorList{} 268 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta).Prefix("metadata")...) 269 allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec).Prefix("spec")...) 270 return allErrs 271 } 272 273 func ValidateDeployment(obj *experimental.Deployment) errs.ValidationErrorList { 274 allErrs := errs.ValidationErrorList{} 275 allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName).Prefix("metadata")...) 276 allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec).Prefix("spec")...) 277 return allErrs 278 } 279 280 func ValidateThirdPartyResourceDataUpdate(old, update *experimental.ThirdPartyResourceData) errs.ValidationErrorList { 281 return ValidateThirdPartyResourceData(update) 282 } 283 284 func ValidateThirdPartyResourceData(obj *experimental.ThirdPartyResourceData) errs.ValidationErrorList { 285 allErrs := errs.ValidationErrorList{} 286 if len(obj.Name) == 0 { 287 allErrs = append(allErrs, errs.NewFieldInvalid("name", obj.Name, "name must be non-empty")) 288 } 289 return allErrs 290 } 291 292 func ValidateJob(job *experimental.Job) errs.ValidationErrorList { 293 allErrs := errs.ValidationErrorList{} 294 // Jobs and rcs have the same name validation 295 allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName).Prefix("metadata")...) 296 allErrs = append(allErrs, ValidateJobSpec(&job.Spec).Prefix("spec")...) 297 return allErrs 298 } 299 300 func ValidateJobSpec(spec *experimental.JobSpec) errs.ValidationErrorList { 301 allErrs := errs.ValidationErrorList{} 302 303 if spec.Parallelism != nil && *spec.Parallelism < 0 { 304 allErrs = append(allErrs, errs.NewFieldInvalid("parallelism", spec.Parallelism, isNegativeErrorMsg)) 305 } 306 if spec.Completions != nil && *spec.Completions < 0 { 307 allErrs = append(allErrs, errs.NewFieldInvalid("completions", spec.Completions, isNegativeErrorMsg)) 308 } 309 310 selector := labels.Set(spec.Selector).AsSelector() 311 if selector.Empty() { 312 allErrs = append(allErrs, errs.NewFieldRequired("selector")) 313 } 314 315 if spec.Template == nil { 316 allErrs = append(allErrs, errs.NewFieldRequired("template")) 317 } else { 318 labels := labels.Set(spec.Template.Labels) 319 if !selector.Matches(labels) { 320 allErrs = append(allErrs, errs.NewFieldInvalid("template.labels", spec.Template.Labels, "selector does not match template")) 321 } 322 allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template).Prefix("template")...) 323 if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && 324 spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { 325 allErrs = append(allErrs, errs.NewFieldValueNotSupported("template.spec.restartPolicy", 326 spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)})) 327 } 328 } 329 return allErrs 330 } 331 332 func ValidateJobStatus(status *experimental.JobStatus) errs.ValidationErrorList { 333 allErrs := errs.ValidationErrorList{} 334 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Active), "active")...) 335 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Successful), "successful")...) 336 allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Unsuccessful), "unsuccessful")...) 337 return allErrs 338 } 339 340 func ValidateJobUpdate(oldJob, job *experimental.Job) errs.ValidationErrorList { 341 allErrs := errs.ValidationErrorList{} 342 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta).Prefix("metadata")...) 343 allErrs = append(allErrs, ValidateJobSpecUpdate(oldJob.Spec, job.Spec).Prefix("spec")...) 344 return allErrs 345 } 346 347 func ValidateJobUpdateStatus(oldJob, job *experimental.Job) errs.ValidationErrorList { 348 allErrs := errs.ValidationErrorList{} 349 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta).Prefix("metadata")...) 350 allErrs = append(allErrs, ValidateJobStatusUpdate(oldJob.Status, job.Status).Prefix("status")...) 351 return allErrs 352 } 353 354 func ValidateJobSpecUpdate(oldSpec, spec experimental.JobSpec) errs.ValidationErrorList { 355 allErrs := errs.ValidationErrorList{} 356 allErrs = append(allErrs, ValidateJobSpec(&spec)...) 357 if !api.Semantic.DeepEqual(oldSpec.Completions, spec.Completions) { 358 allErrs = append(allErrs, errs.NewFieldInvalid("completions", spec.Completions, "field is immutable")) 359 } 360 if !api.Semantic.DeepEqual(oldSpec.Selector, spec.Selector) { 361 allErrs = append(allErrs, errs.NewFieldInvalid("selector", spec.Selector, "field is immutable")) 362 } 363 if !api.Semantic.DeepEqual(oldSpec.Template, spec.Template) { 364 allErrs = append(allErrs, errs.NewFieldInvalid("template", "[omitted]", "field is immutable")) 365 } 366 return allErrs 367 } 368 369 func ValidateJobStatusUpdate(oldStatus, status experimental.JobStatus) errs.ValidationErrorList { 370 allErrs := errs.ValidationErrorList{} 371 allErrs = append(allErrs, ValidateJobStatus(&status)...) 372 return allErrs 373 }