mirror of https://github.com/k3s-io/k3s
Validation of HPA custom metrics annotation
parent
ca30f38697
commit
9a74a60413
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -25,6 +26,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
apivalidation "k8s.io/kubernetes/pkg/api/validation"
|
apivalidation "k8s.io/kubernetes/pkg/api/validation"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/podautoscaler"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/util/intstr"
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
@ -83,9 +85,34 @@ func ValidateSubresourceReference(ref extensions.SubresourceReference, fldPath *
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateHorizontalPodAutoscalerAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if annotationValue, found := annotations[podautoscaler.HpaCustomMetricsTargetAnnotationName]; found {
|
||||||
|
// Try to parse the annotation
|
||||||
|
var targetList extensions.CustomMetricTargetList
|
||||||
|
if err := json.Unmarshal([]byte(annotationValue), &targetList); err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations"), annotations, "failed to parse custom metrics target annotation"))
|
||||||
|
} else {
|
||||||
|
if len(targetList.Items) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("annotations", "items"), "custom metrics target must not be empty"))
|
||||||
|
}
|
||||||
|
for _, target := range targetList.Items {
|
||||||
|
if target.Name == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("annotations", "items", "name"), "missing custom metric target name"))
|
||||||
|
}
|
||||||
|
if target.TargetValue.MilliValue() <= 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations", "items", "value"), target.TargetValue, "custom metric target value must be greater than 0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateHorizontalPodAutoscaler(autoscaler *extensions.HorizontalPodAutoscaler) field.ErrorList {
|
func ValidateHorizontalPodAutoscaler(autoscaler *extensions.HorizontalPodAutoscaler) field.ErrorList {
|
||||||
allErrs := apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, field.NewPath("metadata"))
|
allErrs := apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, field.NewPath("metadata"))
|
||||||
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"))...)
|
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"))...)
|
||||||
|
allErrs = append(allErrs, validateHorizontalPodAutoscalerAnnotations(autoscaler.Annotations, field.NewPath("metadata"))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/podautoscaler"
|
||||||
"k8s.io/kubernetes/pkg/util/intstr"
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,6 +60,24 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
|
||||||
MaxReplicas: 5,
|
MaxReplicas: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "myautoscaler",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
podautoscaler.HpaCustomMetricsTargetAnnotationName: "{\"items\":[{\"name\":\"qps\",\"value\":\"20\"}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: extensions.HorizontalPodAutoscalerSpec{
|
||||||
|
ScaleRef: extensions.SubresourceReference{
|
||||||
|
Kind: "ReplicationController",
|
||||||
|
Name: "myrc",
|
||||||
|
Subresource: "scale",
|
||||||
|
},
|
||||||
|
MinReplicas: newInt(1),
|
||||||
|
MaxReplicas: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, successCase := range successCases {
|
for _, successCase := range successCases {
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 {
|
||||||
|
@ -203,6 +222,90 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
|
||||||
},
|
},
|
||||||
msg: "must be greater than 0",
|
msg: "must be greater than 0",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "myautoscaler",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
podautoscaler.HpaCustomMetricsTargetAnnotationName: "broken",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: extensions.HorizontalPodAutoscalerSpec{
|
||||||
|
ScaleRef: extensions.SubresourceReference{
|
||||||
|
Kind: "ReplicationController",
|
||||||
|
Name: "myrc",
|
||||||
|
Subresource: "scale",
|
||||||
|
},
|
||||||
|
MinReplicas: newInt(1),
|
||||||
|
MaxReplicas: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
msg: "failed to parse custom metrics target annotation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "myautoscaler",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
podautoscaler.HpaCustomMetricsTargetAnnotationName: "{}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: extensions.HorizontalPodAutoscalerSpec{
|
||||||
|
ScaleRef: extensions.SubresourceReference{
|
||||||
|
Kind: "ReplicationController",
|
||||||
|
Name: "myrc",
|
||||||
|
Subresource: "scale",
|
||||||
|
},
|
||||||
|
MinReplicas: newInt(1),
|
||||||
|
MaxReplicas: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
msg: "custom metrics target must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "myautoscaler",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
podautoscaler.HpaCustomMetricsTargetAnnotationName: "{\"items\":[{\"value\":\"20\"}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: extensions.HorizontalPodAutoscalerSpec{
|
||||||
|
ScaleRef: extensions.SubresourceReference{
|
||||||
|
Kind: "ReplicationController",
|
||||||
|
Name: "myrc",
|
||||||
|
Subresource: "scale",
|
||||||
|
},
|
||||||
|
MinReplicas: newInt(1),
|
||||||
|
MaxReplicas: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
msg: "missing custom metric target name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "myautoscaler",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
podautoscaler.HpaCustomMetricsTargetAnnotationName: "{\"items\":[{\"name\":\"qps\",\"value\":\"0\"}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: extensions.HorizontalPodAutoscalerSpec{
|
||||||
|
ScaleRef: extensions.SubresourceReference{
|
||||||
|
Kind: "ReplicationController",
|
||||||
|
Name: "myrc",
|
||||||
|
Subresource: "scale",
|
||||||
|
},
|
||||||
|
MinReplicas: newInt(1),
|
||||||
|
MaxReplicas: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
msg: "custom metric target value must be greater than 0",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range errorCases {
|
for _, c := range errorCases {
|
||||||
|
|
|
@ -39,7 +39,7 @@ const (
|
||||||
// TODO: make it a flag or HPA spec element.
|
// TODO: make it a flag or HPA spec element.
|
||||||
tolerance = 0.1
|
tolerance = 0.1
|
||||||
|
|
||||||
HpaCustomMetricsDefinitionAnnotationName = "alpha/definiton.custom-metrics.podautoscaler.kubernetes.io"
|
HpaCustomMetricsTargetAnnotationName = "alpha/target.custom-metrics.podautoscaler.kubernetes.io"
|
||||||
HpaCustomMetricsStatusAnnotationName = "alpha/status.custom-metrics.podautoscaler.kubernetes.io"
|
HpaCustomMetricsStatusAnnotationName = "alpha/status.custom-metrics.podautoscaler.kubernetes.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ func (a *HorizontalController) reconcileAutoscaler(hpa extensions.HorizontalPodA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmAnnotation, cmAnnotationFound := hpa.Annotations[HpaCustomMetricsDefinitionAnnotationName]; cmAnnotationFound {
|
if cmAnnotation, cmAnnotationFound := hpa.Annotations[HpaCustomMetricsTargetAnnotationName]; cmAnnotationFound {
|
||||||
cmDesiredReplicas, cmStatus, cmTimestamp, err = a.computeReplicasForCustomMetrics(hpa, scale, cmAnnotation)
|
cmDesiredReplicas, cmStatus, cmTimestamp, err = a.computeReplicasForCustomMetrics(hpa, scale, cmAnnotation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.eventRecorder.Event(&hpa, api.EventTypeWarning, "FailedComputeCMReplicas", err.Error())
|
a.eventRecorder.Event(&hpa, api.EventTypeWarning, "FailedComputeCMReplicas", err.Error())
|
||||||
|
|
|
@ -110,7 +110,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
|
||||||
t.Fatalf("Failed to marshal cm: %v", err)
|
t.Fatalf("Failed to marshal cm: %v", err)
|
||||||
}
|
}
|
||||||
obj.Items[0].Annotations = make(map[string]string)
|
obj.Items[0].Annotations = make(map[string]string)
|
||||||
obj.Items[0].Annotations[HpaCustomMetricsDefinitionAnnotationName] = string(b)
|
obj.Items[0].Annotations[HpaCustomMetricsTargetAnnotationName] = string(b)
|
||||||
}
|
}
|
||||||
return true, obj, nil
|
return true, obj, nil
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue