diff --git a/pkg/expapi/deep_copy_generated.go b/pkg/expapi/deep_copy_generated.go index da038264d9..f310ff9f75 100644 --- a/pkg/expapi/deep_copy_generated.go +++ b/pkg/expapi/deep_copy_generated.go @@ -21,10 +21,18 @@ import ( time "time" api "k8s.io/kubernetes/pkg/api" + resource "k8s.io/kubernetes/pkg/api/resource" conversion "k8s.io/kubernetes/pkg/conversion" util "k8s.io/kubernetes/pkg/util" + inf "speter.net/go/exp/math/dec/inf" ) +func deepCopy_api_ListMeta(in api.ListMeta, out *api.ListMeta, c *conversion.Cloner) error { + out.SelfLink = in.SelfLink + out.ResourceVersion = in.ResourceVersion + return nil +} + func deepCopy_api_ObjectMeta(in api.ObjectMeta, out *api.ObjectMeta, c *conversion.Cloner) error { out.Name = in.Name out.GenerateName = in.GenerateName @@ -69,6 +77,72 @@ func deepCopy_api_TypeMeta(in api.TypeMeta, out *api.TypeMeta, c *conversion.Clo return nil } +func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c *conversion.Cloner) error { + if in.Amount != nil { + if newVal, err := c.DeepCopy(in.Amount); err != nil { + return err + } else if newVal == nil { + out.Amount = nil + } else { + out.Amount = newVal.(*inf.Dec) + } + } else { + out.Amount = nil + } + out.Format = in.Format + return nil +} + +func deepCopy_expapi_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { + if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := deepCopy_expapi_HorizontalPodAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + return nil +} + +func deepCopy_expapi_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, c *conversion.Cloner) error { + if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_api_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]HorizontalPodAutoscaler, len(in.Items)) + for i := range in.Items { + if err := deepCopy_expapi_HorizontalPodAutoscaler(in.Items[i], &out.Items[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func deepCopy_expapi_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, c *conversion.Cloner) error { + if in.ScaleRef != nil { + out.ScaleRef = new(SubresourceReference) + if err := deepCopy_expapi_SubresourceReference(*in.ScaleRef, out.ScaleRef, c); err != nil { + return err + } + } else { + out.ScaleRef = nil + } + out.MinCount = in.MinCount + out.MaxCount = in.MaxCount + if err := deepCopy_expapi_TargetConsumption(in.Target, &out.Target, c); err != nil { + return err + } + return nil +} + func deepCopy_expapi_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err @@ -110,6 +184,23 @@ func deepCopy_expapi_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion return nil } +func deepCopy_expapi_SubresourceReference(in SubresourceReference, out *SubresourceReference, c *conversion.Cloner) error { + out.Kind = in.Kind + out.Namespace = in.Namespace + out.Name = in.Name + out.APIVersion = in.APIVersion + out.Subresource = in.Subresource + return nil +} + +func deepCopy_expapi_TargetConsumption(in TargetConsumption, out *TargetConsumption, c *conversion.Cloner) error { + out.Resource = in.Resource + if err := deepCopy_resource_Quantity(in.Quantity, &out.Quantity, c); err != nil { + return err + } + return nil +} + func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.Time); err != nil { return err @@ -121,12 +212,19 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro func init() { err := api.Scheme.AddGeneratedDeepCopyFuncs( + deepCopy_api_ListMeta, deepCopy_api_ObjectMeta, deepCopy_api_TypeMeta, + deepCopy_resource_Quantity, + deepCopy_expapi_HorizontalPodAutoscaler, + deepCopy_expapi_HorizontalPodAutoscalerList, + deepCopy_expapi_HorizontalPodAutoscalerSpec, deepCopy_expapi_ReplicationControllerDummy, deepCopy_expapi_Scale, deepCopy_expapi_ScaleSpec, deepCopy_expapi_ScaleStatus, + deepCopy_expapi_SubresourceReference, + deepCopy_expapi_TargetConsumption, deepCopy_util_Time, ) if err != nil { diff --git a/pkg/expapi/register.go b/pkg/expapi/register.go index 581b1d315c..93f07ac00e 100644 --- a/pkg/expapi/register.go +++ b/pkg/expapi/register.go @@ -27,8 +27,15 @@ func init() { // Adds the list of known types to api.Scheme. func addKnownTypes() { - api.Scheme.AddKnownTypes("", &Scale{}, &ReplicationControllerDummy{}) + api.Scheme.AddKnownTypes("", + &HorizontalPodAutoscaler{}, + &HorizontalPodAutoscalerList{}, + &ReplicationControllerDummy{}, + &Scale{}, + ) } -func (*Scale) IsAnAPIObject() {} -func (*ReplicationControllerDummy) IsAnAPIObject() {} +func (*HorizontalPodAutoscaler) IsAnAPIObject() {} +func (*HorizontalPodAutoscalerList) IsAnAPIObject() {} +func (*ReplicationControllerDummy) IsAnAPIObject() {} +func (*Scale) IsAnAPIObject() {} diff --git a/pkg/expapi/types.go b/pkg/expapi/types.go index 6accffcffe..70d55bf661 100644 --- a/pkg/expapi/types.go +++ b/pkg/expapi/types.go @@ -28,7 +28,10 @@ support is experimental. package expapi -import "k8s.io/kubernetes/pkg/api" +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" +) // ScaleSpec describes the attributes a Scale subresource type ScaleSpec struct { @@ -61,3 +64,49 @@ type Scale struct { type ReplicationControllerDummy struct { api.TypeMeta `json:",inline"` } + +// SubresourceReference contains enough information to let you inspect or modify the referred subresource. +type SubresourceReference struct { + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` + APIVersion string `json:"apiVersion,omitempty"` + Subresource string `json:"subresource,omitempty"` +} + +// TargetConsumption is an object for specifying target average resource consumption of a particular resource. +type TargetConsumption struct { + Resource api.ResourceName `json:"resource,omitempty"` + Quantity resource.Quantity `json:"quantity,omitempty"` +} + +// HorizontalPodAutoscalerSpec is the specification of a horizontal pod autoscaler. +type HorizontalPodAutoscalerSpec struct { + // ScaleRef is a reference to Scale subresource. HorizontalPodAutoscaler will learn the current resource consumption from its status, + // and will set the desired number of pods by modyfying its spec. + ScaleRef *SubresourceReference `json:"scaleRef"` + // MinCount is the lower limit for the number of pods that can be set by the autoscaler. + MinCount int `json:"minCount"` + // MaxCount is the upper limit for the number of pods that can be set by the autoscaler. It cannot be smaller than MinCount. + MaxCount int `json:"maxCount"` + // Target is the target average consumption of the given resource that the autoscaler will try to maintain by adjusting the desired number of pods. + // Currently two types of resources are supported: "cpu" and "memory". + Target TargetConsumption `json:"target"` +} + +// HorizontalPodAutoscaler represents the configuration of a horizontal pod autoscaler. +type HorizontalPodAutoscaler struct { + api.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the behaviour of autoscaler. + Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty"` +} + +// HorizontalPodAutoscaler is a collection of pod autoscalers. +type HorizontalPodAutoscalerList struct { + api.TypeMeta `json:",inline"` + api.ListMeta `json:"metadata,omitempty"` + + Items []HorizontalPodAutoscaler `json:"items"` +} diff --git a/pkg/expapi/v1/deep_copy_generated.go b/pkg/expapi/v1/deep_copy_generated.go index c60f6ffcc1..59bc78b6dd 100644 --- a/pkg/expapi/v1/deep_copy_generated.go +++ b/pkg/expapi/v1/deep_copy_generated.go @@ -21,11 +21,79 @@ import ( time "time" api "k8s.io/kubernetes/pkg/api" + resource "k8s.io/kubernetes/pkg/api/resource" v1 "k8s.io/kubernetes/pkg/api/v1" conversion "k8s.io/kubernetes/pkg/conversion" util "k8s.io/kubernetes/pkg/util" + inf "speter.net/go/exp/math/dec/inf" ) +func deepCopy_api_ListMeta(in api.ListMeta, out *api.ListMeta, c *conversion.Cloner) error { + out.SelfLink = in.SelfLink + out.ResourceVersion = in.ResourceVersion + return nil +} + +func deepCopy_api_ObjectMeta(in api.ObjectMeta, out *api.ObjectMeta, c *conversion.Cloner) error { + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = in.UID + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + if err := deepCopy_util_Time(in.CreationTimestamp, &out.CreationTimestamp, c); err != nil { + return err + } + if in.DeletionTimestamp != nil { + out.DeletionTimestamp = new(util.Time) + if err := deepCopy_util_Time(*in.DeletionTimestamp, out.DeletionTimestamp, c); err != nil { + return err + } + } else { + out.DeletionTimestamp = nil + } + if in.Labels != nil { + out.Labels = make(map[string]string) + for key, val := range in.Labels { + out.Labels[key] = val + } + } else { + out.Labels = nil + } + if in.Annotations != nil { + out.Annotations = make(map[string]string) + for key, val := range in.Annotations { + out.Annotations[key] = val + } + } else { + out.Annotations = nil + } + return nil +} + +func deepCopy_api_TypeMeta(in api.TypeMeta, out *api.TypeMeta, c *conversion.Cloner) error { + out.Kind = in.Kind + out.APIVersion = in.APIVersion + return nil +} + +func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c *conversion.Cloner) error { + if in.Amount != nil { + if newVal, err := c.DeepCopy(in.Amount); err != nil { + return err + } else if newVal == nil { + out.Amount = nil + } else { + out.Amount = newVal.(*inf.Dec) + } + } else { + out.Amount = nil + } + out.Format = in.Format + return nil +} + func deepCopy_v1_ObjectMeta(in v1.ObjectMeta, out *v1.ObjectMeta, c *conversion.Cloner) error { out.Name = in.Name out.GenerateName = in.GenerateName @@ -70,6 +138,56 @@ func deepCopy_v1_TypeMeta(in v1.TypeMeta, out *v1.TypeMeta, c *conversion.Cloner return nil } +func deepCopy_v1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { + if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := deepCopy_v1_HorizontalPodAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + return nil +} + +func deepCopy_v1_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, c *conversion.Cloner) error { + if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_api_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]HorizontalPodAutoscaler, len(in.Items)) + for i := range in.Items { + if err := deepCopy_v1_HorizontalPodAutoscaler(in.Items[i], &out.Items[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func deepCopy_v1_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, c *conversion.Cloner) error { + if in.ScaleRef != nil { + out.ScaleRef = new(SubresourceReference) + if err := deepCopy_v1_SubresourceReference(*in.ScaleRef, out.ScaleRef, c); err != nil { + return err + } + } else { + out.ScaleRef = nil + } + out.MinCount = in.MinCount + out.MaxCount = in.MaxCount + if err := deepCopy_v1_TargetConsumption(in.Target, &out.Target, c); err != nil { + return err + } + return nil +} + func deepCopy_v1_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err @@ -111,6 +229,23 @@ func deepCopy_v1_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion.Clo return nil } +func deepCopy_v1_SubresourceReference(in SubresourceReference, out *SubresourceReference, c *conversion.Cloner) error { + out.Kind = in.Kind + out.Namespace = in.Namespace + out.Name = in.Name + out.APIVersion = in.APIVersion + out.Subresource = in.Subresource + return nil +} + +func deepCopy_v1_TargetConsumption(in TargetConsumption, out *TargetConsumption, c *conversion.Cloner) error { + out.Resource = in.Resource + if err := deepCopy_resource_Quantity(in.Quantity, &out.Quantity, c); err != nil { + return err + } + return nil +} + func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.Time); err != nil { return err @@ -122,12 +257,21 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro func init() { err := api.Scheme.AddGeneratedDeepCopyFuncs( + deepCopy_api_ListMeta, + deepCopy_api_ObjectMeta, + deepCopy_api_TypeMeta, + deepCopy_resource_Quantity, deepCopy_v1_ObjectMeta, deepCopy_v1_TypeMeta, + deepCopy_v1_HorizontalPodAutoscaler, + deepCopy_v1_HorizontalPodAutoscalerList, + deepCopy_v1_HorizontalPodAutoscalerSpec, deepCopy_v1_ReplicationControllerDummy, deepCopy_v1_Scale, deepCopy_v1_ScaleSpec, deepCopy_v1_ScaleStatus, + deepCopy_v1_SubresourceReference, + deepCopy_v1_TargetConsumption, deepCopy_util_Time, ) if err != nil { diff --git a/pkg/expapi/v1/register.go b/pkg/expapi/v1/register.go index 19da3fea77..1ad15196db 100644 --- a/pkg/expapi/v1/register.go +++ b/pkg/expapi/v1/register.go @@ -24,15 +24,22 @@ import ( var Codec = runtime.CodecFor(api.Scheme, "v1") func init() { - addConversionFuncs() - addDefaultingFuncs() addKnownTypes() + addDefaultingFuncs() + addConversionFuncs() } // Adds the list of known types to api.Scheme. func addKnownTypes() { - api.Scheme.AddKnownTypes("v1", &Scale{}, &ReplicationControllerDummy{}) + api.Scheme.AddKnownTypes("v1", + &HorizontalPodAutoscaler{}, + &HorizontalPodAutoscalerList{}, + &ReplicationControllerDummy{}, + &Scale{}, + ) } -func (*Scale) IsAnAPIObject() {} -func (*ReplicationControllerDummy) IsAnAPIObject() {} +func (*HorizontalPodAutoscaler) IsAnAPIObject() {} +func (*HorizontalPodAutoscalerList) IsAnAPIObject() {} +func (*ReplicationControllerDummy) IsAnAPIObject() {} +func (*Scale) IsAnAPIObject() {} diff --git a/pkg/expapi/v1/types.go b/pkg/expapi/v1/types.go index bd68981c26..19fa480915 100644 --- a/pkg/expapi/v1/types.go +++ b/pkg/expapi/v1/types.go @@ -16,7 +16,11 @@ limitations under the License. package v1 -import "k8s.io/kubernetes/pkg/api/v1" +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/v1" +) // ScaleSpec describes the attributes a Scale subresource type ScaleSpec struct { @@ -49,3 +53,49 @@ type Scale struct { type ReplicationControllerDummy struct { v1.TypeMeta `json:",inline"` } + +// SubresourceReference contains enough information to let you inspect or modify the referred subresource. +type SubresourceReference struct { + Kind string `json:"kind,omitempty" description:"kind of the referent; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"` + Namespace string `json:"namespace,omitempty" description:"namespace of the referent; see http://releases.k8s.io/HEAD/docs/user-guide/namespaces.md"` + Name string `json:"name,omitempty" description:"name of the referent; see http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names"` + APIVersion string `json:"apiVersion,omitempty" description:"API version of the referent"` + Subresource string `json:"subresource,omitempty" decription:"subresource name of the referent"` +} + +// TargetConsumption is an object for specifying target average resource consumption of a particular resource. +type TargetConsumption struct { + Resource api.ResourceName `json:"resource,omitempty"` + Quantity resource.Quantity `json:"quantity,omitempty"` +} + +// HorizontalPodAutoscalerSpec is the specification of a horizontal pod autoscaler. +type HorizontalPodAutoscalerSpec struct { + // ScaleRef is a reference to Scale subresource. HorizontalPodAutoscaler will learn the current resource consumption from its status, + // and will set the desired number of pods by modyfying its spec. + ScaleRef *SubresourceReference `json:"scaleRef" description:"reference to scale subresource for quering the current resource cosumption and for setting the desired number of pods"` + // MinCount is the lower limit for the number of pods that can be set by the autoscaler. + MinCount int `json:"minCount" description:"lower limit for the number of pods"` + // MaxCount is the upper limit for the number of pods that can be set by the autoscaler. It cannot be smaller than MinCount. + MaxCount int `json:"maxCount" description:"upper limit for the number of pods"` + // Target is the target average consumption of the given resource that the autoscaler will try to maintain by adjusting the desired number of pods. + // Currently two types of resources are supported: "cpu" and "memory". + Target TargetConsumption `json:"target" description:"target average consumption of resource that the autoscaler will try to maintain by adjusting the desired number of pods"` +} + +// HorizontalPodAutoscaler represents the configuration of a horizontal pod autoscaler. +type HorizontalPodAutoscaler struct { + api.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the behaviour of autoscaler. + Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty" description:"specification of the desired behavior of the autoscaler; http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status"` +} + +// HorizontalPodAutoscaler is a collection of pod autoscalers. +type HorizontalPodAutoscalerList struct { + api.TypeMeta `json:",inline"` + api.ListMeta `json:"metadata,omitempty"` + + Items []HorizontalPodAutoscaler `json:"items" description:"list of horizontal pod autoscalers"` +} diff --git a/pkg/expapi/validation/validation.go b/pkg/expapi/validation/validation.go new file mode 100644 index 0000000000..b61300e4e4 --- /dev/null +++ b/pkg/expapi/validation/validation.go @@ -0,0 +1,67 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "k8s.io/kubernetes/pkg/api" + apivalidation "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/expapi" + errs "k8s.io/kubernetes/pkg/util/fielderrors" +) + +// ValidateHorizontalPodAutoscaler can be used to check whether the given autoscaler name is valid. +// Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed. +func ValidateHorizontalPodAutoscalerName(name string, prefix bool) (bool, string) { + // TODO: finally move it to pkg/api/validation and use nameIsDNSSubdomain function + return apivalidation.ValidateReplicationControllerName(name, prefix) +} + +func validateHorizontalPodAutoscalerSpec(autoscaler expapi.HorizontalPodAutoscalerSpec) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + if autoscaler.MinCount < 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("minCount", autoscaler.MinCount, `must be non-negative`)) + } + if autoscaler.MaxCount < autoscaler.MinCount { + allErrs = append(allErrs, errs.NewFieldInvalid("maxCount", autoscaler.MaxCount, `must be bigger or equal to minCount`)) + } + if autoscaler.ScaleRef == nil { + allErrs = append(allErrs, errs.NewFieldRequired("scaleRef")) + } + resource := autoscaler.Target.Resource.String() + if resource != string(api.ResourceMemory) && resource != string(api.ResourceCPU) { + allErrs = append(allErrs, errs.NewFieldInvalid("target.resource", resource, "resource not supported by autoscaler")) + } + quantity := autoscaler.Target.Quantity.Value() + if quantity < 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("target.quantity", quantity, "must be non-negative")) + } + return allErrs +} + +func ValidateHorizontalPodAutoscaler(autoscaler *expapi.HorizontalPodAutoscaler) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName).Prefix("metadata")...) + allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec)...) + return allErrs +} + +func ValidateHorizontalPodAutoscalerUpdate(newAutoscler, oldAutoscaler *expapi.HorizontalPodAutoscaler) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&newAutoscler.ObjectMeta, &oldAutoscaler.ObjectMeta).Prefix("metadata")...) + allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscler.Spec)...) + return allErrs +} diff --git a/pkg/expapi/validation/validation_test.go b/pkg/expapi/validation/validation_test.go new file mode 100644 index 0000000000..e7a8f72f4e --- /dev/null +++ b/pkg/expapi/validation/validation_test.go @@ -0,0 +1,129 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/expapi" +) + +func TestValidateHorizontalPodAutoscaler(t *testing.T) { + successCases := []expapi.HorizontalPodAutoscaler{ + { + ObjectMeta: api.ObjectMeta{ + Name: "myautoscaler", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + ScaleRef: &expapi.SubresourceReference{ + Subresource: "scale", + }, + MinCount: 1, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceCPU, resource.MustParse("0.8")}, + }, + }, + } + for _, successCase := range successCases { + if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]expapi.HorizontalPodAutoscaler{ + "must be non-negative": { + ObjectMeta: api.ObjectMeta{ + Name: "myautoscaler", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + ScaleRef: &expapi.SubresourceReference{ + Subresource: "scale", + }, + MinCount: -1, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceCPU, resource.MustParse("0.8")}, + }, + }, + "must be bigger or equal to minCount": { + ObjectMeta: api.ObjectMeta{ + Name: "myautoscaler", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + ScaleRef: &expapi.SubresourceReference{ + Subresource: "scale", + }, + MinCount: 7, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceCPU, resource.MustParse("0.8")}, + }, + }, + "invalid value": { + ObjectMeta: api.ObjectMeta{ + Name: "myautoscaler", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + ScaleRef: &expapi.SubresourceReference{ + Subresource: "scale", + }, + MinCount: 1, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceCPU, resource.MustParse("-0.8")}, + }, + }, + "resource not supported": { + ObjectMeta: api.ObjectMeta{ + Name: "myautoscaler", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + ScaleRef: &expapi.SubresourceReference{ + Subresource: "scale", + }, + MinCount: 1, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceName("NotSupportedResource"), resource.MustParse("0.8")}, + }, + }, + "required value": { + ObjectMeta: api.ObjectMeta{ + Name: "myautoscaler", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + MinCount: 1, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceCPU, resource.MustParse("0.8")}, + }, + }, + } + + for k, v := range errorCases { + errs := ValidateHorizontalPodAutoscaler(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } else if !strings.Contains(errs[0].Error(), k) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], k) + } + } +} diff --git a/pkg/master/master.go b/pkg/master/master.go index 4f598a7d7b..f6f6f14016 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -77,6 +77,8 @@ import ( "k8s.io/kubernetes/pkg/ui" "k8s.io/kubernetes/pkg/util" + horizontalpodautoscaleretcd "k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd" + "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" "github.com/golang/glog" @@ -777,11 +779,13 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion { // expapi returns the resources and codec for the experimental api func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion { - controllerStorage := expcontrolleretcd.NewStorage(c.DatabaseStorage) + autoscalerStorage := horizontalpodautoscaleretcd.NewREST(c.DatabaseStorage) + storage := map[string]rest.Storage{ strings.ToLower("replicationControllers"): controllerStorage.ReplicationController, strings.ToLower("replicationControllers/scale"): controllerStorage.Scale, + strings.ToLower("horizontalpodautoscalers"): autoscalerStorage, } return &apiserver.APIGroupVersion{ diff --git a/pkg/registry/horizontalpodautoscaler/doc.go b/pkg/registry/horizontalpodautoscaler/doc.go new file mode 100644 index 0000000000..a628ee1b94 --- /dev/null +++ b/pkg/registry/horizontalpodautoscaler/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package horizontalpodautoscaler diff --git a/pkg/registry/horizontalpodautoscaler/etcd/etcd.go b/pkg/registry/horizontalpodautoscaler/etcd/etcd.go new file mode 100644 index 0000000000..96b637b8d5 --- /dev/null +++ b/pkg/registry/horizontalpodautoscaler/etcd/etcd.go @@ -0,0 +1,73 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" +) + +// rest implements a RESTStorage for horizontal pod autoscalers against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers. +func NewREST(s storage.Interface) *REST { + var prefix = "/horizontalpodautoscalers" + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &expapi.HorizontalPodAutoscaler{} }, + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: func() runtime.Object { return &expapi.HorizontalPodAutoscalerList{} }, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of an autoscaler + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*expapi.HorizontalPodAutoscaler).Name, nil + }, + // Used to match objects based on labels/fields for list + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return horizontalpodautoscaler.MatchAutoscaler(label, field) + }, + EndpointName: "horizontalPodAutoscalers", + + // Used to validate autoscaler creation + CreateStrategy: horizontalpodautoscaler.Strategy, + + // Used to validate autoscaler updates + UpdateStrategy: horizontalpodautoscaler.Strategy, + + Storage: s, + } + + return &REST{store} +} diff --git a/pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go b/pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go new file mode 100644 index 0000000000..7f611393c1 --- /dev/null +++ b/pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go @@ -0,0 +1,231 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/rest/resttest" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/v1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" + "k8s.io/kubernetes/pkg/tools" + "k8s.io/kubernetes/pkg/tools/etcdtest" + + "github.com/coreos/go-etcd/etcd" +) + +var scheme *runtime.Scheme +var codec runtime.Codec + +func init() { + // Ensure that expapi/v1 packege is used, so that it will get initialized and register HorizontalPodAutoscaler object. + dummy := v1.HorizontalPodAutoscaler{} + dummy.Spec = v1.HorizontalPodAutoscalerSpec{} +} + +func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, storage.Interface) { + fakeEtcdClient := tools.NewFakeEtcdClient(t) + fakeEtcdClient.TestIndex = true + etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix()) + storage := NewREST(etcdStorage) + return storage, fakeEtcdClient, etcdStorage +} + +func validNewHorizontalPodAutoscaler(name string) *expapi.HorizontalPodAutoscaler { + return &expapi.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Spec: expapi.HorizontalPodAutoscalerSpec{ + ScaleRef: &expapi.SubresourceReference{ + Subresource: "scale", + }, + MinCount: 1, + MaxCount: 5, + Target: expapi.TargetConsumption{api.ResourceCPU, resource.MustParse("0.8")}, + }, + } +} + +func TestCreate(t *testing.T) { + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + autoscaler := validNewHorizontalPodAutoscaler("foo") + autoscaler.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + autoscaler, + // invalid + &expapi.HorizontalPodAutoscaler{}, + ) +} + +func TestUpdate(t *testing.T) { + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + key, err := storage.KeyFunc(test.TestContext(), "foo") + if err != nil { + t.Fatal(err) + } + key = etcdtest.AddPrefix(key) + fakeEtcdClient.ExpectNotFoundGet(key) + fakeEtcdClient.ChangeIndex = 2 + autoscaler := validNewHorizontalPodAutoscaler("foo") + existing := validNewHorizontalPodAutoscaler("exists") + existing.Namespace = test.TestNamespace() + obj, err := storage.Create(test.TestContext(), existing) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + older := obj.(*expapi.HorizontalPodAutoscaler) + older.ResourceVersion = "1" + test.TestUpdate( + autoscaler, + existing, + older, + ) +} + +func TestDelete(t *testing.T) { + ctx := api.NewDefaultContext() + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + autoscaler := validNewHorizontalPodAutoscaler("foo2") + key, _ := storage.KeyFunc(ctx, "foo2") + key = etcdtest.AddPrefix(key) + createFn := func() runtime.Object { + fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{ + R: &etcd.Response{ + Node: &etcd.Node{ + Value: runtime.EncodeOrDie(testapi.Codec(), autoscaler), + ModifiedIndex: 1, + }, + }, + } + return autoscaler + } + gracefulSetFn := func() bool { + if fakeEtcdClient.Data[key].R.Node == nil { + return false + } + return fakeEtcdClient.Data[key].R.Node.TTL == 30 + } + test.TestDeleteNoGraceful(createFn, gracefulSetFn) +} + +func TestEtcdGet(t *testing.T) { + ctx := api.NewDefaultContext() + registry, fakeClient, _ := newStorage(t) + autoscaler := validNewHorizontalPodAutoscaler("foo3") + name := autoscaler.Name + key, _ := registry.KeyFunc(ctx, name) + key = etcdtest.AddPrefix(key) + fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), autoscaler), 0) + response, err := fakeClient.Get(key, false, false) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + var autoscalerOut expapi.HorizontalPodAutoscaler + err = testapi.Codec().DecodeInto([]byte(response.Node.Value), &autoscalerOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + obj, err := registry.Get(ctx, name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + got := obj.(*expapi.HorizontalPodAutoscaler) + autoscaler.ObjectMeta.ResourceVersion = got.ObjectMeta.ResourceVersion + if e, a := autoscaler, got; !api.Semantic.DeepEqual(*e, *a) { + t.Errorf("Unexpected autoscaler: %#v, expected %#v", e, a) + } +} + +func TestEmptyList(t *testing.T) { + ctx := api.NewDefaultContext() + registry, fakeClient, _ := newStorage(t) + fakeClient.ChangeIndex = 1 + key := registry.KeyRootFunc(ctx) + key = etcdtest.AddPrefix(key) + fakeClient.Data[key] = tools.EtcdResponseWithError{ + R: &etcd.Response{}, + E: fakeClient.NewError(tools.EtcdErrorCodeNotFound), + } + autoscalerList, err := registry.List(ctx, labels.Everything(), fields.Everything()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(autoscalerList.(*expapi.HorizontalPodAutoscalerList).Items) != 0 { + t.Errorf("Unexpected non-zero autoscaler list: %#v", autoscalerList) + } + if autoscalerList.(*expapi.HorizontalPodAutoscalerList).ResourceVersion != "1" { + t.Errorf("Unexpected resource version: %#v", autoscalerList) + } +} + +func TestList(t *testing.T) { + ctx := api.NewDefaultContext() + registry, fakeClient, _ := newStorage(t) + fakeClient.ChangeIndex = 1 + key := registry.KeyRootFunc(ctx) + key = etcdtest.AddPrefix(key) + fakeClient.Data[key] = tools.EtcdResponseWithError{ + R: &etcd.Response{ + Node: &etcd.Node{ + Nodes: []*etcd.Node{ + { + Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + }), + }, + { + Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{Name: "bar"}, + }), + }, + }, + }, + }, + } + obj, err := registry.List(ctx, labels.Everything(), fields.Everything()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + autoscalerList := obj.(*expapi.HorizontalPodAutoscalerList) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(autoscalerList.Items) != 2 { + t.Errorf("Unexpected HorizontalPodAutoscaler list: %#v", autoscalerList) + } + if autoscalerList.Items[0].Name != "foo" { + t.Errorf("Unexpected HorizontalPodAutoscaler: %#v", autoscalerList.Items[0]) + } + if autoscalerList.Items[1].Name != "bar" { + t.Errorf("Unexpected HorizontalPodAutoscaler: %#v", autoscalerList.Items[1]) + } +} diff --git a/pkg/registry/horizontalpodautoscaler/registry.go b/pkg/registry/horizontalpodautoscaler/registry.go new file mode 100644 index 0000000000..d9fb7cc2d6 --- /dev/null +++ b/pkg/registry/horizontalpodautoscaler/registry.go @@ -0,0 +1,80 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package horizontalpodautoscaler + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" +) + +// Registry is an interface implemented by things that know how to store HorizontalPodAutoscaler objects. +type Registry interface { + // ListPersistentVolumes obtains a list of autoscalers having labels which match selector. + ListHorizontalPodAutoscaler(ctx api.Context, selector labels.Selector) (*expapi.HorizontalPodAutoscalerList, error) + // Get a specific autoscaler + GetHorizontalPodAutoscaler(ctx api.Context, autoscalerID string) (*expapi.HorizontalPodAutoscaler, error) + // Create an autoscaler based on a specification. + CreateHorizontalPodAutoscaler(ctx api.Context, autoscaler *expapi.HorizontalPodAutoscaler) error + // Update an existing autoscaler + UpdateHorizontalPodAutoscaler(ctx api.Context, autoscaler *expapi.HorizontalPodAutoscaler) error + // Delete an existing autoscaler + DeleteHorizontalPodAutoscaler(ctx api.Context, autoscalerID string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewREST returns a new Registry interface for the given Storage. Any mismatched types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListHorizontalPodAutoscaler(ctx api.Context, label labels.Selector) (*expapi.HorizontalPodAutoscalerList, error) { + obj, err := s.List(ctx, label, fields.Everything()) + if err != nil { + return nil, err + } + return obj.(*expapi.HorizontalPodAutoscalerList), nil +} + +func (s *storage) GetHorizontalPodAutoscaler(ctx api.Context, autoscalerID string) (*expapi.HorizontalPodAutoscaler, error) { + obj, err := s.Get(ctx, autoscalerID) + if err != nil { + return nil, err + } + return obj.(*expapi.HorizontalPodAutoscaler), nil +} + +func (s *storage) CreateHorizontalPodAutoscaler(ctx api.Context, autoscaler *expapi.HorizontalPodAutoscaler) error { + _, err := s.Create(ctx, autoscaler) + return err +} + +func (s *storage) UpdateHorizontalPodAutoscaler(ctx api.Context, autoscaler *expapi.HorizontalPodAutoscaler) error { + _, _, err := s.Update(ctx, autoscaler) + return err +} + +func (s *storage) DeleteHorizontalPodAutoscaler(ctx api.Context, autoscalerID string) error { + _, err := s.Delete(ctx, autoscalerID, nil) + return err +} diff --git a/pkg/registry/horizontalpodautoscaler/rest.go b/pkg/registry/horizontalpodautoscaler/rest.go new file mode 100644 index 0000000000..3a5487eb4e --- /dev/null +++ b/pkg/registry/horizontalpodautoscaler/rest.go @@ -0,0 +1,86 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package horizontalpodautoscaler + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + errs "k8s.io/kubernetes/pkg/util/fielderrors" +) + +// autoscalerStrategy implements behavior for HorizontalPodAutoscalers +type autoscalerStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating HorizontalPodAutoscaler +// objects via the REST API. +var Strategy = autoscalerStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for autoscaler. +func (autoscalerStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (autoscalerStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*expapi.HorizontalPodAutoscaler) +} + +// Validate validates a new autoscaler. +func (autoscalerStrategy) Validate(ctx api.Context, obj runtime.Object) errs.ValidationErrorList { + autoscaler := obj.(*expapi.HorizontalPodAutoscaler) + return validation.ValidateHorizontalPodAutoscaler(autoscaler) +} + +// AllowCreateOnUpdate is false for autoscalers. +func (autoscalerStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (autoscalerStrategy) PrepareForUpdate(obj, old runtime.Object) { + _ = obj.(*expapi.HorizontalPodAutoscaler) +} + +// ValidateUpdate is the default update validation for an end user. +func (autoscalerStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList { + return validation.ValidateHorizontalPodAutoscalerUpdate(obj.(*expapi.HorizontalPodAutoscaler), old.(*expapi.HorizontalPodAutoscaler)) +} + +func (autoscalerStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// MatchAutoscaler returns a generic matcher for a given label and field selector. +func MatchAutoscaler(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + autoscaler, ok := obj.(*expapi.HorizontalPodAutoscaler) + if !ok { + return false, fmt.Errorf("not a horizontal pod autoscaler") + } + return label.Matches(labels.Set(autoscaler.Labels)), nil + }) +}