...

Source file src/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/apis/autoscaling/validation

     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  
    22  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    23  	pathvalidation "k8s.io/apimachinery/pkg/api/validation/path"
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    27  	"k8s.io/kubernetes/pkg/apis/autoscaling"
    28  	corevalidation "k8s.io/kubernetes/pkg/apis/core/v1/validation"
    29  	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    30  	"k8s.io/kubernetes/pkg/features"
    31  )
    32  
    33  const (
    34  	// MaxPeriodSeconds is the largest allowed scaling policy period (in seconds)
    35  	MaxPeriodSeconds int32 = 1800
    36  	// MaxStabilizationWindowSeconds is the largest allowed stabilization window (in seconds)
    37  	MaxStabilizationWindowSeconds int32 = 3600
    38  )
    39  
    40  // ValidateScale validates a Scale and returns an ErrorList with any errors.
    41  func ValidateScale(scale *autoscaling.Scale) field.ErrorList {
    42  	allErrs := field.ErrorList{}
    43  	allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
    44  
    45  	if scale.Spec.Replicas < 0 {
    46  		allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "replicas"), scale.Spec.Replicas, "must be greater than or equal to 0"))
    47  	}
    48  
    49  	return allErrs
    50  }
    51  
    52  // ValidateHorizontalPodAutoscalerName can be used to check whether the given autoscaler name is valid.
    53  // Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed.
    54  var ValidateHorizontalPodAutoscalerName = apivalidation.ValidateReplicationControllerName
    55  
    56  func validateHorizontalPodAutoscalerSpec(autoscaler autoscaling.HorizontalPodAutoscalerSpec, fldPath *field.Path, minReplicasLowerBound int32) field.ErrorList {
    57  	allErrs := field.ErrorList{}
    58  
    59  	if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < minReplicasLowerBound {
    60  		allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), *autoscaler.MinReplicas,
    61  			fmt.Sprintf("must be greater than or equal to %d", minReplicasLowerBound)))
    62  	}
    63  	if autoscaler.MaxReplicas < 1 {
    64  		allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than 0"))
    65  	}
    66  	if autoscaler.MinReplicas != nil && autoscaler.MaxReplicas < *autoscaler.MinReplicas {
    67  		allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than or equal to `minReplicas`"))
    68  	}
    69  	if refErrs := ValidateCrossVersionObjectReference(autoscaler.ScaleTargetRef, fldPath.Child("scaleTargetRef")); len(refErrs) > 0 {
    70  		allErrs = append(allErrs, refErrs...)
    71  	}
    72  	if refErrs := validateMetrics(autoscaler.Metrics, fldPath.Child("metrics"), autoscaler.MinReplicas); len(refErrs) > 0 {
    73  		allErrs = append(allErrs, refErrs...)
    74  	}
    75  	if refErrs := validateBehavior(autoscaler.Behavior, fldPath.Child("behavior")); len(refErrs) > 0 {
    76  		allErrs = append(allErrs, refErrs...)
    77  	}
    78  	return allErrs
    79  }
    80  
    81  // ValidateCrossVersionObjectReference validates a CrossVersionObjectReference and returns an
    82  // ErrorList with any errors.
    83  func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectReference, fldPath *field.Path) field.ErrorList {
    84  	allErrs := field.ErrorList{}
    85  	if len(ref.Kind) == 0 {
    86  		allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
    87  	} else {
    88  		for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Kind) {
    89  			allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, msg))
    90  		}
    91  	}
    92  
    93  	if len(ref.Name) == 0 {
    94  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
    95  	} else {
    96  		for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Name) {
    97  			allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg))
    98  		}
    99  	}
   100  
   101  	return allErrs
   102  }
   103  
   104  // ValidateHorizontalPodAutoscaler validates a HorizontalPodAutoscaler and returns an
   105  // ErrorList with any errors.
   106  func ValidateHorizontalPodAutoscaler(autoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList {
   107  	allErrs := apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, field.NewPath("metadata"))
   108  
   109  	// MinReplicasLowerBound represents a minimum value for minReplicas
   110  	// 0 when HPA scale-to-zero feature is enabled
   111  	var minReplicasLowerBound int32
   112  
   113  	if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) {
   114  		minReplicasLowerBound = 0
   115  	} else {
   116  		minReplicasLowerBound = 1
   117  	}
   118  	allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"), minReplicasLowerBound)...)
   119  	return allErrs
   120  }
   121  
   122  // ValidateHorizontalPodAutoscalerUpdate validates an update to a HorizontalPodAutoscaler and returns an
   123  // ErrorList with any errors.
   124  func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList {
   125  	allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata"))
   126  
   127  	// minReplicasLowerBound represents a minimum value for minReplicas
   128  	// 0 when HPA scale-to-zero feature is enabled or HPA object already has minReplicas=0
   129  	var minReplicasLowerBound int32
   130  
   131  	if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) || (oldAutoscaler.Spec.MinReplicas != nil && *oldAutoscaler.Spec.MinReplicas == 0) {
   132  		minReplicasLowerBound = 0
   133  	} else {
   134  		minReplicasLowerBound = 1
   135  	}
   136  
   137  	allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscaler.Spec, field.NewPath("spec"), minReplicasLowerBound)...)
   138  	return allErrs
   139  }
   140  
   141  // ValidateHorizontalPodAutoscalerStatusUpdate validates an update to status on a HorizontalPodAutoscaler and
   142  // returns an ErrorList with any errors.
   143  func ValidateHorizontalPodAutoscalerStatusUpdate(newAutoscaler, oldAutoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList {
   144  	allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata"))
   145  	status := newAutoscaler.Status
   146  	allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), field.NewPath("status", "currentReplicas"))...)
   147  	allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredReplicas), field.NewPath("status", "desiredReplicas"))...)
   148  	return allErrs
   149  }
   150  
   151  func validateMetrics(metrics []autoscaling.MetricSpec, fldPath *field.Path, minReplicas *int32) field.ErrorList {
   152  	allErrs := field.ErrorList{}
   153  	hasObjectMetrics := false
   154  	hasExternalMetrics := false
   155  
   156  	for i, metricSpec := range metrics {
   157  		idxPath := fldPath.Index(i)
   158  		if targetErrs := validateMetricSpec(metricSpec, idxPath); len(targetErrs) > 0 {
   159  			allErrs = append(allErrs, targetErrs...)
   160  		}
   161  		if metricSpec.Type == autoscaling.ObjectMetricSourceType {
   162  			hasObjectMetrics = true
   163  		}
   164  		if metricSpec.Type == autoscaling.ExternalMetricSourceType {
   165  			hasExternalMetrics = true
   166  		}
   167  	}
   168  
   169  	if minReplicas != nil && *minReplicas == 0 {
   170  		if !hasObjectMetrics && !hasExternalMetrics {
   171  			allErrs = append(allErrs, field.Forbidden(fldPath, "must specify at least one Object or External metric to support scaling to zero replicas"))
   172  		}
   173  	}
   174  
   175  	return allErrs
   176  }
   177  
   178  func validateBehavior(behavior *autoscaling.HorizontalPodAutoscalerBehavior, fldPath *field.Path) field.ErrorList {
   179  	allErrs := field.ErrorList{}
   180  	if behavior != nil {
   181  		if scaleUpErrs := validateScalingRules(behavior.ScaleUp, fldPath.Child("scaleUp")); len(scaleUpErrs) > 0 {
   182  			allErrs = append(allErrs, scaleUpErrs...)
   183  		}
   184  		if scaleDownErrs := validateScalingRules(behavior.ScaleDown, fldPath.Child("scaleDown")); len(scaleDownErrs) > 0 {
   185  			allErrs = append(allErrs, scaleDownErrs...)
   186  		}
   187  	}
   188  	return allErrs
   189  }
   190  
   191  var validSelectPolicyTypes = sets.NewString(string(autoscaling.MaxPolicySelect), string(autoscaling.MinPolicySelect), string(autoscaling.DisabledPolicySelect))
   192  var validSelectPolicyTypesList = validSelectPolicyTypes.List()
   193  
   194  func validateScalingRules(rules *autoscaling.HPAScalingRules, fldPath *field.Path) field.ErrorList {
   195  	allErrs := field.ErrorList{}
   196  	if rules != nil {
   197  		if rules.StabilizationWindowSeconds != nil && *rules.StabilizationWindowSeconds < 0 {
   198  			allErrs = append(allErrs, field.Invalid(fldPath.Child("stabilizationWindowSeconds"), rules.StabilizationWindowSeconds, "must be greater than or equal to zero"))
   199  		}
   200  		if rules.StabilizationWindowSeconds != nil && *rules.StabilizationWindowSeconds > MaxStabilizationWindowSeconds {
   201  			allErrs = append(allErrs, field.Invalid(fldPath.Child("stabilizationWindowSeconds"), rules.StabilizationWindowSeconds,
   202  				fmt.Sprintf("must be less than or equal to %v", MaxStabilizationWindowSeconds)))
   203  		}
   204  		if rules.SelectPolicy != nil && !validSelectPolicyTypes.Has(string(*rules.SelectPolicy)) {
   205  			allErrs = append(allErrs, field.NotSupported(fldPath.Child("selectPolicy"), rules.SelectPolicy, validSelectPolicyTypesList))
   206  		}
   207  		policiesPath := fldPath.Child("policies")
   208  		if len(rules.Policies) == 0 {
   209  			allErrs = append(allErrs, field.Required(policiesPath, "must specify at least one Policy"))
   210  		}
   211  		for i, policy := range rules.Policies {
   212  			idxPath := policiesPath.Index(i)
   213  			if policyErrs := validateScalingPolicy(policy, idxPath); len(policyErrs) > 0 {
   214  				allErrs = append(allErrs, policyErrs...)
   215  			}
   216  		}
   217  	}
   218  	return allErrs
   219  }
   220  
   221  var validPolicyTypes = sets.NewString(string(autoscaling.PodsScalingPolicy), string(autoscaling.PercentScalingPolicy))
   222  var validPolicyTypesList = validPolicyTypes.List()
   223  
   224  func validateScalingPolicy(policy autoscaling.HPAScalingPolicy, fldPath *field.Path) field.ErrorList {
   225  	allErrs := field.ErrorList{}
   226  	if policy.Type != autoscaling.PodsScalingPolicy && policy.Type != autoscaling.PercentScalingPolicy {
   227  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), policy.Type, validPolicyTypesList))
   228  	}
   229  	if policy.Value <= 0 {
   230  		allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), policy.Value, "must be greater than zero"))
   231  	}
   232  	if policy.PeriodSeconds <= 0 {
   233  		allErrs = append(allErrs, field.Invalid(fldPath.Child("periodSeconds"), policy.PeriodSeconds, "must be greater than zero"))
   234  	}
   235  	if policy.PeriodSeconds > MaxPeriodSeconds {
   236  		allErrs = append(allErrs, field.Invalid(fldPath.Child("periodSeconds"), policy.PeriodSeconds,
   237  			fmt.Sprintf("must be less than or equal to %v", MaxPeriodSeconds)))
   238  	}
   239  	return allErrs
   240  }
   241  
   242  var validMetricSourceTypes = sets.NewString(
   243  	string(autoscaling.ObjectMetricSourceType), string(autoscaling.PodsMetricSourceType),
   244  	string(autoscaling.ResourceMetricSourceType), string(autoscaling.ExternalMetricSourceType),
   245  	string(autoscaling.ContainerResourceMetricSourceType))
   246  var validMetricSourceTypesList = validMetricSourceTypes.List()
   247  
   248  func validateMetricSpec(spec autoscaling.MetricSpec, fldPath *field.Path) field.ErrorList {
   249  	allErrs := field.ErrorList{}
   250  
   251  	if len(string(spec.Type)) == 0 {
   252  		allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric source type"))
   253  	}
   254  
   255  	if !validMetricSourceTypes.Has(string(spec.Type)) {
   256  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, validMetricSourceTypesList))
   257  	}
   258  
   259  	typesPresent := sets.NewString()
   260  	if spec.Object != nil {
   261  		typesPresent.Insert("object")
   262  		if typesPresent.Len() == 1 {
   263  			allErrs = append(allErrs, validateObjectSource(spec.Object, fldPath.Child("object"))...)
   264  		}
   265  	}
   266  
   267  	if spec.External != nil {
   268  		typesPresent.Insert("external")
   269  		if typesPresent.Len() == 1 {
   270  			allErrs = append(allErrs, validateExternalSource(spec.External, fldPath.Child("external"))...)
   271  		}
   272  	}
   273  
   274  	if spec.Pods != nil {
   275  		typesPresent.Insert("pods")
   276  		if typesPresent.Len() == 1 {
   277  			allErrs = append(allErrs, validatePodsSource(spec.Pods, fldPath.Child("pods"))...)
   278  		}
   279  	}
   280  
   281  	if spec.Resource != nil {
   282  		typesPresent.Insert("resource")
   283  		if typesPresent.Len() == 1 {
   284  			allErrs = append(allErrs, validateResourceSource(spec.Resource, fldPath.Child("resource"))...)
   285  		}
   286  	}
   287  
   288  	if spec.ContainerResource != nil {
   289  		typesPresent.Insert("containerResource")
   290  		if typesPresent.Len() == 1 {
   291  			allErrs = append(allErrs, validateContainerResourceSource(spec.ContainerResource, fldPath.Child("containerResource"))...)
   292  		}
   293  	}
   294  
   295  	var expectedField string
   296  	switch spec.Type {
   297  
   298  	case autoscaling.ObjectMetricSourceType:
   299  		if spec.Object == nil {
   300  			allErrs = append(allErrs, field.Required(fldPath.Child("object"), "must populate information for the given metric source"))
   301  		}
   302  		expectedField = "object"
   303  	case autoscaling.PodsMetricSourceType:
   304  		if spec.Pods == nil {
   305  			allErrs = append(allErrs, field.Required(fldPath.Child("pods"), "must populate information for the given metric source"))
   306  		}
   307  		expectedField = "pods"
   308  	case autoscaling.ResourceMetricSourceType:
   309  		if spec.Resource == nil {
   310  			allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "must populate information for the given metric source"))
   311  		}
   312  		expectedField = "resource"
   313  	case autoscaling.ExternalMetricSourceType:
   314  		if spec.External == nil {
   315  			allErrs = append(allErrs, field.Required(fldPath.Child("external"), "must populate information for the given metric source"))
   316  		}
   317  		expectedField = "external"
   318  	case autoscaling.ContainerResourceMetricSourceType:
   319  		if spec.ContainerResource == nil {
   320  			allErrs = append(allErrs, field.Required(fldPath.Child("containerResource"), "must populate information for the given metric source"))
   321  		}
   322  		expectedField = "containerResource"
   323  	default:
   324  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, validMetricSourceTypesList))
   325  	}
   326  
   327  	if typesPresent.Len() != 1 {
   328  		typesPresent.Delete(expectedField)
   329  		for typ := range typesPresent {
   330  			allErrs = append(allErrs, field.Forbidden(fldPath.Child(typ), "must populate the given metric source only"))
   331  		}
   332  	}
   333  
   334  	return allErrs
   335  }
   336  
   337  func validateObjectSource(src *autoscaling.ObjectMetricSource, fldPath *field.Path) field.ErrorList {
   338  	allErrs := field.ErrorList{}
   339  
   340  	allErrs = append(allErrs, ValidateCrossVersionObjectReference(src.DescribedObject, fldPath.Child("describedObject"))...)
   341  	allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...)
   342  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   343  
   344  	if src.Target.Value == nil && src.Target.AverageValue == nil {
   345  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value or averageValue"))
   346  	}
   347  
   348  	return allErrs
   349  }
   350  
   351  func validateExternalSource(src *autoscaling.ExternalMetricSource, fldPath *field.Path) field.ErrorList {
   352  	allErrs := field.ErrorList{}
   353  
   354  	allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...)
   355  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   356  
   357  	if src.Target.Value == nil && src.Target.AverageValue == nil {
   358  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value for metric or a per-pod target"))
   359  	}
   360  
   361  	if src.Target.Value != nil && src.Target.AverageValue != nil {
   362  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("value"), "may not set both a target value for metric and a per-pod target"))
   363  	}
   364  
   365  	return allErrs
   366  }
   367  
   368  func validatePodsSource(src *autoscaling.PodsMetricSource, fldPath *field.Path) field.ErrorList {
   369  	allErrs := field.ErrorList{}
   370  
   371  	allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...)
   372  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   373  
   374  	if src.Target.AverageValue == nil {
   375  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must specify a positive target averageValue"))
   376  	}
   377  
   378  	return allErrs
   379  }
   380  
   381  func validateContainerResourceSource(src *autoscaling.ContainerResourceMetricSource, fldPath *field.Path) field.ErrorList {
   382  	allErrs := field.ErrorList{}
   383  
   384  	if len(src.Name) == 0 {
   385  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a resource name"))
   386  	} else {
   387  		allErrs = append(allErrs, corevalidation.ValidateContainerResourceName(src.Name, fldPath.Child("name"))...)
   388  	}
   389  
   390  	if len(src.Container) == 0 {
   391  		allErrs = append(allErrs, field.Required(fldPath.Child("container"), "must specify a container"))
   392  	} else {
   393  		allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(src.Container, fldPath.Child("container"))...)
   394  	}
   395  
   396  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   397  
   398  	if src.Target.AverageUtilization == nil && src.Target.AverageValue == nil {
   399  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageUtilization"), "must set either a target raw value or a target utilization"))
   400  	}
   401  
   402  	if src.Target.AverageUtilization != nil && src.Target.AverageValue != nil {
   403  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("averageValue"), "may not set both a target raw value and a target utilization"))
   404  	}
   405  
   406  	return allErrs
   407  }
   408  
   409  func validateResourceSource(src *autoscaling.ResourceMetricSource, fldPath *field.Path) field.ErrorList {
   410  	allErrs := field.ErrorList{}
   411  
   412  	if len(src.Name) == 0 {
   413  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a resource name"))
   414  	}
   415  
   416  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   417  
   418  	if src.Target.AverageUtilization == nil && src.Target.AverageValue == nil {
   419  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageUtilization"), "must set either a target raw value or a target utilization"))
   420  	}
   421  
   422  	if src.Target.AverageUtilization != nil && src.Target.AverageValue != nil {
   423  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("averageValue"), "may not set both a target raw value and a target utilization"))
   424  	}
   425  
   426  	return allErrs
   427  }
   428  
   429  func validateMetricTarget(mt autoscaling.MetricTarget, fldPath *field.Path) field.ErrorList {
   430  	allErrs := field.ErrorList{}
   431  
   432  	if len(mt.Type) == 0 {
   433  		allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric target type"))
   434  	}
   435  
   436  	if mt.Type != autoscaling.UtilizationMetricType &&
   437  		mt.Type != autoscaling.ValueMetricType &&
   438  		mt.Type != autoscaling.AverageValueMetricType {
   439  		allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), mt.Type, "must be either Utilization, Value, or AverageValue"))
   440  	}
   441  
   442  	if mt.Value != nil && mt.Value.Sign() != 1 {
   443  		allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), mt.Value, "must be positive"))
   444  	}
   445  
   446  	if mt.AverageValue != nil && mt.AverageValue.Sign() != 1 {
   447  		allErrs = append(allErrs, field.Invalid(fldPath.Child("averageValue"), mt.AverageValue, "must be positive"))
   448  	}
   449  
   450  	if mt.AverageUtilization != nil && *mt.AverageUtilization < 1 {
   451  		allErrs = append(allErrs, field.Invalid(fldPath.Child("averageUtilization"), mt.AverageUtilization, "must be greater than 0"))
   452  	}
   453  
   454  	return allErrs
   455  }
   456  
   457  func validateMetricIdentifier(id autoscaling.MetricIdentifier, fldPath *field.Path) field.ErrorList {
   458  	allErrs := field.ErrorList{}
   459  
   460  	if len(id.Name) == 0 {
   461  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a metric name"))
   462  	} else {
   463  		for _, msg := range pathvalidation.IsValidPathSegmentName(id.Name) {
   464  			allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), id.Name, msg))
   465  		}
   466  	}
   467  	return allErrs
   468  }
   469  

View as plain text