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  }