k8s.io/kubernetes@v1.29.3/pkg/apis/autoscaling/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  
    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  			if utilfeature.DefaultFeatureGate.Enabled(features.HPAContainerMetrics) {
   321  				allErrs = append(allErrs, field.Required(fldPath.Child("containerResource"), "must populate information for the given metric source"))
   322  			} else {
   323  				allErrs = append(allErrs, field.Required(fldPath.Child("containerResource"), "must populate information for the given metric source (only allowed when HPAContainerMetrics feature is enabled)"))
   324  			}
   325  		}
   326  		expectedField = "containerResource"
   327  	default:
   328  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, validMetricSourceTypesList))
   329  	}
   330  
   331  	if typesPresent.Len() != 1 {
   332  		typesPresent.Delete(expectedField)
   333  		for typ := range typesPresent {
   334  			allErrs = append(allErrs, field.Forbidden(fldPath.Child(typ), "must populate the given metric source only"))
   335  		}
   336  	}
   337  
   338  	return allErrs
   339  }
   340  
   341  func validateObjectSource(src *autoscaling.ObjectMetricSource, fldPath *field.Path) field.ErrorList {
   342  	allErrs := field.ErrorList{}
   343  
   344  	allErrs = append(allErrs, ValidateCrossVersionObjectReference(src.DescribedObject, fldPath.Child("describedObject"))...)
   345  	allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...)
   346  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   347  
   348  	if src.Target.Value == nil && src.Target.AverageValue == nil {
   349  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value or averageValue"))
   350  	}
   351  
   352  	return allErrs
   353  }
   354  
   355  func validateExternalSource(src *autoscaling.ExternalMetricSource, fldPath *field.Path) field.ErrorList {
   356  	allErrs := field.ErrorList{}
   357  
   358  	allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...)
   359  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   360  
   361  	if src.Target.Value == nil && src.Target.AverageValue == nil {
   362  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value for metric or a per-pod target"))
   363  	}
   364  
   365  	if src.Target.Value != nil && src.Target.AverageValue != nil {
   366  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("value"), "may not set both a target value for metric and a per-pod target"))
   367  	}
   368  
   369  	return allErrs
   370  }
   371  
   372  func validatePodsSource(src *autoscaling.PodsMetricSource, fldPath *field.Path) field.ErrorList {
   373  	allErrs := field.ErrorList{}
   374  
   375  	allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...)
   376  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   377  
   378  	if src.Target.AverageValue == nil {
   379  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must specify a positive target averageValue"))
   380  	}
   381  
   382  	return allErrs
   383  }
   384  
   385  func validateContainerResourceSource(src *autoscaling.ContainerResourceMetricSource, fldPath *field.Path) field.ErrorList {
   386  	allErrs := field.ErrorList{}
   387  
   388  	if len(src.Name) == 0 {
   389  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a resource name"))
   390  	} else {
   391  		allErrs = append(allErrs, corevalidation.ValidateContainerResourceName(src.Name, fldPath.Child("name"))...)
   392  	}
   393  
   394  	if len(src.Container) == 0 {
   395  		allErrs = append(allErrs, field.Required(fldPath.Child("container"), "must specify a container"))
   396  	} else {
   397  		allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(src.Container, fldPath.Child("container"))...)
   398  	}
   399  
   400  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   401  
   402  	if src.Target.AverageUtilization == nil && src.Target.AverageValue == nil {
   403  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageUtilization"), "must set either a target raw value or a target utilization"))
   404  	}
   405  
   406  	if src.Target.AverageUtilization != nil && src.Target.AverageValue != nil {
   407  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("averageValue"), "may not set both a target raw value and a target utilization"))
   408  	}
   409  
   410  	return allErrs
   411  }
   412  
   413  func validateResourceSource(src *autoscaling.ResourceMetricSource, fldPath *field.Path) field.ErrorList {
   414  	allErrs := field.ErrorList{}
   415  
   416  	if len(src.Name) == 0 {
   417  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a resource name"))
   418  	}
   419  
   420  	allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...)
   421  
   422  	if src.Target.AverageUtilization == nil && src.Target.AverageValue == nil {
   423  		allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageUtilization"), "must set either a target raw value or a target utilization"))
   424  	}
   425  
   426  	if src.Target.AverageUtilization != nil && src.Target.AverageValue != nil {
   427  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("averageValue"), "may not set both a target raw value and a target utilization"))
   428  	}
   429  
   430  	return allErrs
   431  }
   432  
   433  func validateMetricTarget(mt autoscaling.MetricTarget, fldPath *field.Path) field.ErrorList {
   434  	allErrs := field.ErrorList{}
   435  
   436  	if len(mt.Type) == 0 {
   437  		allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric target type"))
   438  	}
   439  
   440  	if mt.Type != autoscaling.UtilizationMetricType &&
   441  		mt.Type != autoscaling.ValueMetricType &&
   442  		mt.Type != autoscaling.AverageValueMetricType {
   443  		allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), mt.Type, "must be either Utilization, Value, or AverageValue"))
   444  	}
   445  
   446  	if mt.Value != nil && mt.Value.Sign() != 1 {
   447  		allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), mt.Value, "must be positive"))
   448  	}
   449  
   450  	if mt.AverageValue != nil && mt.AverageValue.Sign() != 1 {
   451  		allErrs = append(allErrs, field.Invalid(fldPath.Child("averageValue"), mt.AverageValue, "must be positive"))
   452  	}
   453  
   454  	if mt.AverageUtilization != nil && *mt.AverageUtilization < 1 {
   455  		allErrs = append(allErrs, field.Invalid(fldPath.Child("averageUtilization"), mt.AverageUtilization, "must be greater than 0"))
   456  	}
   457  
   458  	return allErrs
   459  }
   460  
   461  func validateMetricIdentifier(id autoscaling.MetricIdentifier, fldPath *field.Path) field.ErrorList {
   462  	allErrs := field.ErrorList{}
   463  
   464  	if len(id.Name) == 0 {
   465  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a metric name"))
   466  	} else {
   467  		for _, msg := range pathvalidation.IsValidPathSegmentName(id.Name) {
   468  			allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), id.Name, msg))
   469  		}
   470  	}
   471  	return allErrs
   472  }