Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add status subresource to HorizontalPodAutoscaler #14568

Merged
merged 1 commit into from
Oct 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions pkg/apis/extensions/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ func ValidateHorizontalPodAutoscalerName(name string, prefix bool) (bool, string
return apivalidation.ValidateReplicationControllerName(name, prefix)
}

func validateResourceConsumption(consumption *extensions.ResourceConsumption, fieldName string) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
resource := consumption.Resource.String()
if resource != string(api.ResourceMemory) && resource != string(api.ResourceCPU) {
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName+".resource", resource, "resource not supported by autoscaler"))
}
quantity := consumption.Quantity.Value()
if quantity < 0 {
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName+".quantity", quantity, "must be non-negative"))
}
return allErrs
}

func validateHorizontalPodAutoscalerSpec(autoscaler extensions.HorizontalPodAutoscalerSpec) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if autoscaler.MinReplicas < 0 {
Expand Down Expand Up @@ -87,6 +100,19 @@ func ValidateHorizontalPodAutoscalerUpdate(newAutoscler, oldAutoscaler *extensio
return allErrs
}

func ValidateHorizontalPodAutoscalerStatusUpdate(controller, oldController *extensions.HorizontalPodAutoscaler) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...)

status := controller.Status
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentReplicas), "currentReplicas")...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredReplicas), "desiredReplicas")...)
if status.CurrentConsumption != nil {
allErrs = append(allErrs, validateResourceConsumption(status.CurrentConsumption, "currentConsumption")...)
}
return allErrs
}

func ValidateThirdPartyResourceUpdate(old, update *extensions.ThirdPartyResource) errs.ValidationErrorList {
return ValidateThirdPartyResource(update)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/client/unversioned/horizontalpodautoscaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type HorizontalPodAutoscalerInterface interface {
Delete(name string, options *api.DeleteOptions) error
Create(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error)
Update(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error)
UpdateStatus(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error)
Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
}

Expand Down Expand Up @@ -94,6 +95,13 @@ func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *extensions.Ho
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, at line 77 above, you may need to do

body, err := api.Scheme.EncodeToVersion(options, latest.GroupOrDie("").GroupVersion)

xref: https://github.com/kubernetes/kubernetes/pull/14566/files#r40605889

No need to do in this PR, but could help if you are debugging issues.

}

// UpdateStatus takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if it occurs.
func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) {
result = &extensions.HorizontalPodAutoscaler{}
err = c.client.Put().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(horizontalPodAutoscaler.Name).SubResource("status").Body(horizontalPodAutoscaler).Do().Into(result)
return
}

// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers.
func (c *horizontalPodAutoscalers) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return c.client.Get().
Expand Down
17 changes: 17 additions & 0 deletions pkg/client/unversioned/horizontalpodautoscaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,23 @@ func TestHorizontalPodAutoscalerUpdate(t *testing.T) {
c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerUpdateStatus(t *testing.T) {
ns := api.NamespaceDefault
horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: ns,
ResourceVersion: "1",
},
}
c := &testClient{
Request: testRequest{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc") + "/status", Query: buildQueryValues(nil)},
Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler},
}
response, err := c.Setup(t).Experimental().HorizontalPodAutoscalers(ns).UpdateStatus(horizontalPodAutoscaler)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client is still Experimental()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I mentioned that to @ncdc just after our chat. The receiver was not renamed in the move from experiemental->extensions.

c.Validate(t, response, err)
}

func TestHorizontalPodAutoscalerDelete(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ func (c *FakeHorizontalPodAutoscalers) Update(a *extensions.HorizontalPodAutosca
return obj.(*extensions.HorizontalPodAutoscaler), err
}

func (c *FakeHorizontalPodAutoscalers) UpdateStatus(a *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) {
obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("horizontalpodautoscalers", "status", c.Namespace, a), &extensions.HorizontalPodAutoscaler{})
if obj == nil {
return nil, err
}
return obj.(*extensions.HorizontalPodAutoscaler), err
}

func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error {
_, err := c.Fake.Invokes(NewDeleteAction("horizontalpodautoscalers", c.Namespace, name), &extensions.HorizontalPodAutoscaler{})
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/podautoscaler/horizontal.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (a *HorizontalController) reconcileAutoscaler(hpa extensions.HorizontalPodA
hpa.Status.LastScaleTimestamp = &now
}

_, err = a.client.Experimental().HorizontalPodAutoscalers(hpa.Namespace).Update(&hpa)
_, err = a.client.Experimental().HorizontalPodAutoscalers(hpa.Namespace).UpdateStatus(&hpa)
if err != nil {
a.eventRecorder.Event(&hpa, "FailedUpdateStatus", err.Error())
return fmt.Errorf("failed to update status for %s: %v", hpa.Name, err)
Expand Down
25 changes: 13 additions & 12 deletions pkg/master/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
dbClient := func(resource string) storage.Interface {
return c.StorageDestinations.get("extensions", resource)
}
autoscalerStorage := horizontalpodautoscaleretcd.NewREST(dbClient("horizonalpodautoscalers"))
autoscalerStorage, autoscalerStatusStorage := horizontalpodautoscaleretcd.NewREST(dbClient("horizonalpodautoscalers"))
thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(dbClient("thirdpartyresources"))
daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(dbClient("daemonsets"))
deploymentStorage := deploymentetcd.NewStorage(dbClient("deployments"))
Expand All @@ -1061,17 +1061,18 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
}, 10*time.Second)
}()
storage := map[string]rest.Storage{
strings.ToLower("replicationControllers"): controllerStorage.ReplicationController,
strings.ToLower("replicationControllers/scale"): controllerStorage.Scale,
strings.ToLower("horizontalpodautoscalers"): autoscalerStorage,
strings.ToLower("thirdpartyresources"): thirdPartyResourceStorage,
strings.ToLower("daemonsets"): daemonSetStorage,
strings.ToLower("daemonsets/status"): daemonSetStatusStorage,
strings.ToLower("deployments"): deploymentStorage.Deployment,
strings.ToLower("deployments/scale"): deploymentStorage.Scale,
strings.ToLower("jobs"): jobStorage,
strings.ToLower("jobs/status"): jobStatusStorage,
strings.ToLower("ingress"): ingressStorage,
strings.ToLower("replicationControllers"): controllerStorage.ReplicationController,
strings.ToLower("replicationControllers/scale"): controllerStorage.Scale,
strings.ToLower("horizontalpodautoscalers"): autoscalerStorage,
strings.ToLower("horizontalpodautoscalers/status"): autoscalerStatusStorage,
strings.ToLower("thirdpartyresources"): thirdPartyResourceStorage,
strings.ToLower("daemonsets"): daemonSetStorage,
strings.ToLower("daemonsets/status"): daemonSetStatusStorage,
strings.ToLower("deployments"): deploymentStorage.Deployment,
strings.ToLower("deployments/scale"): deploymentStorage.Scale,
strings.ToLower("jobs"): jobStorage,
strings.ToLower("jobs/status"): jobStatusStorage,
strings.ToLower("ingress"): ingressStorage,
}

expMeta := latest.GroupOrDie("extensions")
Expand Down
20 changes: 18 additions & 2 deletions pkg/registry/horizontalpodautoscaler/etcd/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type REST struct {
}

// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers.
func NewREST(s storage.Interface) *REST {
func NewREST(s storage.Interface) (*REST, *StatusREST) {
prefix := "/horizontalpodautoscalers"
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &extensions.HorizontalPodAutoscaler{} },
Expand Down Expand Up @@ -67,5 +67,21 @@ func NewREST(s storage.Interface) *REST {

Storage: s,
}
return &REST{store}
statusStore := *store
statusStore.UpdateStrategy = horizontalpodautoscaler.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}
}

// StatusREST implements the REST endpoint for changing the status of a daemonset
type StatusREST struct {
store *etcdgeneric.Etcd
}

func (r *StatusREST) New() runtime.Object {
return &extensions.HorizontalPodAutoscaler{}
}

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}
19 changes: 11 additions & 8 deletions pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ import (
"k8s.io/kubernetes/pkg/tools"
)

func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
func newStorage(t *testing.T) (*REST, *StatusREST, *tools.FakeEtcdClient) {
etcdStorage, fakeClient := registrytest.NewEtcdStorage(t, "extensions")
return NewREST(etcdStorage), fakeClient
storage, statusStorage := NewREST(etcdStorage)
return storage, statusStorage, fakeClient
}

func validNewHorizontalPodAutoscaler(name string) *extensions.HorizontalPodAutoscaler {
Expand All @@ -54,7 +55,7 @@ func validNewHorizontalPodAutoscaler(name string) *extensions.HorizontalPodAutos
}

func TestCreate(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
autoscaler := validNewHorizontalPodAutoscaler("foo")
autoscaler.ObjectMeta = api.ObjectMeta{}
Expand All @@ -67,7 +68,7 @@ func TestCreate(t *testing.T) {
}

func TestUpdate(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestUpdate(
// valid
Expand All @@ -82,25 +83,25 @@ func TestUpdate(t *testing.T) {
}

func TestDelete(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestDelete(validNewHorizontalPodAutoscaler("foo"))
}

func TestGet(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestGet(validNewHorizontalPodAutoscaler("foo"))
}

func TestList(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestList(validNewHorizontalPodAutoscaler("foo"))
}

func TestWatch(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestWatch(
validNewHorizontalPodAutoscaler("foo"),
Expand All @@ -119,3 +120,5 @@ func TestWatch(t *testing.T) {
},
)
}

// TODO TestUpdateStatus
27 changes: 25 additions & 2 deletions pkg/registry/horizontalpodautoscaler/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ func (autoscalerStrategy) NamespaceScoped() bool {

// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (autoscalerStrategy) PrepareForCreate(obj runtime.Object) {
_ = obj.(*extensions.HorizontalPodAutoscaler)
newHPA := obj.(*extensions.HorizontalPodAutoscaler)

// create cannot set status
newHPA.Status = extensions.HorizontalPodAutoscalerStatus{}
}

// Validate validates a new autoscaler.
Expand All @@ -62,7 +65,10 @@ func (autoscalerStrategy) AllowCreateOnUpdate() bool {

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (autoscalerStrategy) PrepareForUpdate(obj, old runtime.Object) {
_ = obj.(*extensions.HorizontalPodAutoscaler)
newHPA := obj.(*extensions.HorizontalPodAutoscaler)
oldHPA := obj.(*extensions.HorizontalPodAutoscaler)
// Update is not allowed to set status
newHPA.Status = oldHPA.Status
}

// ValidateUpdate is the default update validation for an end user.
Expand Down Expand Up @@ -91,3 +97,20 @@ func MatchAutoscaler(label labels.Selector, field fields.Selector) generic.Match
},
}
}

type autoscalerStatusStrategy struct {
autoscalerStrategy
}

var StatusStrategy = autoscalerStatusStrategy{Strategy}

func (autoscalerStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
newAutoscaler := obj.(*extensions.HorizontalPodAutoscaler)
oldAutoscaler := old.(*extensions.HorizontalPodAutoscaler)
// status changes are not allowed to update spec
newAutoscaler.Spec = oldAutoscaler.Spec
}

func (autoscalerStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList {
return validation.ValidateHorizontalPodAutoscalerStatusUpdate(obj.(*extensions.HorizontalPodAutoscaler), old.(*extensions.HorizontalPodAutoscaler))
}