diff --git a/pkg/apis/autoscaling/validation/validation.go b/pkg/apis/autoscaling/validation/validation.go index 6338b2a744e..a8e789cf8a3 100644 --- a/pkg/apis/autoscaling/validation/validation.go +++ b/pkg/apis/autoscaling/validation/validation.go @@ -115,7 +115,7 @@ func validateMetrics(metrics []autoscaling.MetricSpec, fldPath *field.Path) fiel return allErrs } -var validMetricSourceTypes = sets.NewString(string(autoscaling.ObjectMetricSourceType), string(autoscaling.PodsMetricSourceType), string(autoscaling.ResourceMetricSourceType)) +var validMetricSourceTypes = sets.NewString(string(autoscaling.ObjectMetricSourceType), string(autoscaling.PodsMetricSourceType), string(autoscaling.ResourceMetricSourceType), string(autoscaling.ExternalMetricSourceType)) var validMetricSourceTypesList = validMetricSourceTypes.List() func validateMetricSpec(spec autoscaling.MetricSpec, fldPath *field.Path) field.ErrorList { @@ -137,6 +137,13 @@ func validateMetricSpec(spec autoscaling.MetricSpec, fldPath *field.Path) field. } } + if spec.External != nil { + typesPresent.Insert("external") + if typesPresent.Len() == 1 { + allErrs = append(allErrs, validateExternalSource(spec.External, fldPath.Child("external"))...) + } + } + if spec.Pods != nil { typesPresent.Insert("pods") if typesPresent.Len() == 1 { @@ -183,6 +190,32 @@ func validateObjectSource(src *autoscaling.ObjectMetricSource, fldPath *field.Pa return allErrs } +func validateExternalSource(src *autoscaling.ExternalMetricSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(src.MetricName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("metricName"), "must specify a metric name")) + } + + if src.TargetValue == nil && src.TargetAverageValue == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("targetValue"), "must set either a target value for metric or a per-pod target")) + } + + if src.TargetValue != nil && src.TargetAverageValue != nil { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("targetValue"), "may not set both a target value for metric and a per-pod target")) + } + + if src.TargetAverageValue != nil && src.TargetAverageValue.Sign() != 1 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("targetAverageValue"), src.TargetAverageValue, "must be positive")) + } + + if src.TargetValue != nil && src.TargetValue.Sign() != 1 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("targetValue"), src.TargetValue, "must be positive")) + } + + return allErrs +} + func validatePodsSource(src *autoscaling.PodsMetricSource, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/apis/autoscaling/validation/validation_test.go b/pkg/apis/autoscaling/validation/validation_test.go index d334a77b3c9..665afd50209 100644 --- a/pkg/apis/autoscaling/validation/validation_test.go +++ b/pkg/apis/autoscaling/validation/validation_test.go @@ -202,6 +202,62 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { }, }, }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricName: "some/metric", + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + TargetValue: resource.NewMilliQuantity(300, resource.DecimalSI), + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricName: "some/metric", + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + TargetAverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), + }, + }, + }, + }, + }, } for _, successCase := range successCases { if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 { @@ -487,6 +543,130 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { }, msg: "must specify a metric name", }, + { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + TargetValue: resource.NewMilliQuantity(300, resource.DecimalSI), + }, + }, + }, + }, + }, + msg: "must specify a metric name", + }, + { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricName: "some/metric", + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + }, + }, + }, + }, + }, + msg: "must set either a target value for metric or a per-pod target", + }, + { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricName: "some/metric", + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + TargetValue: resource.NewMilliQuantity(-300, resource.DecimalSI), + }, + }, + }, + }, + }, + msg: "must be positive", + }, + { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricName: "some/metric", + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + TargetAverageValue: resource.NewMilliQuantity(-300, resource.DecimalSI), + }, + }, + }, + }, + }, + msg: "must be positive", + }, + { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: newInt32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{ + { + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + MetricName: "some/metric", + MetricSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + }, + }, + TargetValue: resource.NewMilliQuantity(300, resource.DecimalSI), + TargetAverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), + }, + }, + }, + }, + }, + msg: "may not set both a target value for metric and a per-pod target", + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault},