Added HorizontalPodAutoscaler object to experimental API.

Added HorizontalPodAutoscaler object to experimental API. Related to #12087.
pull/6/head
Jerzy Szczepkowski 2015-08-06 16:14:28 +02:00
parent 0ded91c521
commit baa1612241
14 changed files with 1053 additions and 11 deletions

View File

@ -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 {

View File

@ -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() {}

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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() {}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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{

View File

@ -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

View File

@ -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}
}

View File

@ -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])
}
}

View File

@ -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
}

View File

@ -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
})
}