From 8acf01d6205166700754d0d744d15b454bc0669f Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Fri, 16 Oct 2015 18:01:08 -0700 Subject: [PATCH] add status subresource for deployment --- pkg/client/unversioned/deployment.go | 11 ++++-- pkg/client/unversioned/deployment_test.go | 21 +++++++++++ .../testclient/fake_deployments.go | 9 +++++ .../deployment/deployment_controller.go | 2 +- pkg/master/master.go | 1 + pkg/registry/deployment/etcd/etcd.go | 24 +++++++++++-- pkg/registry/deployment/etcd/etcd_test.go | 36 +++++++++++++++++++ pkg/registry/deployment/strategy.go | 23 ++++++++++++ 8 files changed, 121 insertions(+), 6 deletions(-) diff --git a/pkg/client/unversioned/deployment.go b/pkg/client/unversioned/deployment.go index 59897129dba..df27ef4d127 100644 --- a/pkg/client/unversioned/deployment.go +++ b/pkg/client/unversioned/deployment.go @@ -34,8 +34,9 @@ type DeploymentInterface interface { List(label labels.Selector, field fields.Selector) (*extensions.DeploymentList, error) Get(name string) (*extensions.Deployment, error) Delete(name string, options *api.DeleteOptions) error - Create(Deployment *extensions.Deployment) (*extensions.Deployment, error) - Update(Deployment *extensions.Deployment) (*extensions.Deployment, error) + Create(*extensions.Deployment) (*extensions.Deployment, error) + Update(*extensions.Deployment) (*extensions.Deployment, error) + UpdateStatus(*extensions.Deployment) (*extensions.Deployment, error) Watch(label labels.Selector, field fields.Selector, opts api.ListOptions) (watch.Interface, error) } @@ -93,6 +94,12 @@ func (c *deployments) Update(deployment *extensions.Deployment) (result *extensi return } +func (c *deployments) UpdateStatus(deployment *extensions.Deployment) (result *extensions.Deployment, err error) { + result = &extensions.Deployment{} + err = c.client.Put().Namespace(c.ns).Resource("deployments").Name(deployment.Name).SubResource("status").Body(deployment).Do().Into(result) + return +} + // Watch returns a watch.Interface that watches the requested deployments. func (c *deployments) Watch(label labels.Selector, field fields.Selector, opts api.ListOptions) (watch.Interface, error) { return c.client.Get(). diff --git a/pkg/client/unversioned/deployment_test.go b/pkg/client/unversioned/deployment_test.go index d4d596eebaa..8b9cb428496 100644 --- a/pkg/client/unversioned/deployment_test.go +++ b/pkg/client/unversioned/deployment_test.go @@ -124,6 +124,27 @@ func TestDeploymentUpdate(t *testing.T) { c.Validate(t, response, err) } +func TestDeploymentUpdateStatus(t *testing.T) { + ns := api.NamespaceDefault + deployment := &extensions.Deployment{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: ns, + ResourceVersion: "1", + }, + } + c := &testClient{ + Request: testRequest{ + Method: "PUT", + Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, "abc") + "/status", + Query: buildQueryValues(nil), + }, + Response: Response{StatusCode: 200, Body: deployment}, + } + response, err := c.Setup(t).Deployments(ns).UpdateStatus(deployment) + c.Validate(t, response, err) +} + func TestDeploymentDelete(t *testing.T) { ns := api.NamespaceDefault c := &testClient{ diff --git a/pkg/client/unversioned/testclient/fake_deployments.go b/pkg/client/unversioned/testclient/fake_deployments.go index 1cf7f8e69e8..c05d0b5a0df 100644 --- a/pkg/client/unversioned/testclient/fake_deployments.go +++ b/pkg/client/unversioned/testclient/fake_deployments.go @@ -72,6 +72,15 @@ func (c *FakeDeployments) Update(deployment *extensions.Deployment) (*extensions return obj.(*extensions.Deployment), err } +func (c *FakeDeployments) UpdateStatus(deployment *extensions.Deployment) (*extensions.Deployment, error) { + obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("deployments", "status", c.Namespace, deployment), deployment) + if obj == nil { + return nil, err + } + + return obj.(*extensions.Deployment), err +} + func (c *FakeDeployments) Delete(name string, options *api.DeleteOptions) error { _, err := c.Fake.Invokes(NewDeleteAction("deployments", c.Namespace, name), &extensions.Deployment{}) return err diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index 7f95288e27b..01d33843d68 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -251,7 +251,7 @@ func (d *DeploymentController) updateDeploymentStatus(allRCs []*api.ReplicationC Replicas: totalReplicas, UpdatedReplicas: updatedReplicas, } - _, err := d.updateDeployment(&newDeployment) + _, err := d.client.Extensions().Deployments(deployment.ObjectMeta.Namespace).UpdateStatus(&newDeployment) return err } diff --git a/pkg/master/master.go b/pkg/master/master.go index 624f4d7bcd8..b655bf08787 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -1081,6 +1081,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion { if isEnabled("deployments") { deploymentStorage := deploymentetcd.NewStorage(dbClient("deployments")) storage["deployments"] = deploymentStorage.Deployment + storage["deployments/status"] = deploymentStorage.Status storage["deployments/scale"] = deploymentStorage.Scale } if isEnabled("jobs") { diff --git a/pkg/registry/deployment/etcd/etcd.go b/pkg/registry/deployment/etcd/etcd.go index ce349607767..265588d33b0 100644 --- a/pkg/registry/deployment/etcd/etcd.go +++ b/pkg/registry/deployment/etcd/etcd.go @@ -35,15 +35,17 @@ import ( // DeploymentStorage includes dummy storage for Deployments and for Scale subresource. type DeploymentStorage struct { Deployment *REST + Status *StatusREST Scale *ScaleREST } func NewStorage(s storage.Interface) DeploymentStorage { - deploymentRest := NewREST(s) + deploymentRest, deploymentStatusRest := NewREST(s) deploymentRegistry := deployment.NewRegistry(deploymentRest) return DeploymentStorage{ Deployment: deploymentRest, + Status: deploymentStatusRest, Scale: &ScaleREST{registry: &deploymentRegistry}, } } @@ -53,7 +55,7 @@ type REST struct { } // NewREST returns a RESTStorage object that will work against deployments. -func NewREST(s storage.Interface) *REST { +func NewREST(s storage.Interface) (*REST, *StatusREST) { prefix := "/deployments" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &extensions.Deployment{} }, @@ -87,7 +89,23 @@ func NewREST(s storage.Interface) *REST { Storage: s, } - return &REST{store} + statusStore := *store + statusStore.UpdateStrategy = deployment.StatusStrategy + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a deployment +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.Deployment{} +} + +// 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) } type ScaleREST struct { diff --git a/pkg/registry/deployment/etcd/etcd_test.go b/pkg/registry/deployment/etcd/etcd_test.go index a81124969bd..ea184f481b1 100755 --- a/pkg/registry/deployment/etcd/etcd_test.go +++ b/pkg/registry/deployment/etcd/etcd_test.go @@ -236,3 +236,39 @@ func TestScaleUpdate(t *testing.T) { t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) } } + +func TestStatusUpdate(t *testing.T) { + storage, fakeClient := newStorage(t) + + ctx := api.WithNamespace(api.NewContext(), namespace) + key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) + if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Extensions.Codec(), &validDeployment), 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + update := extensions.Deployment{ + ObjectMeta: validDeployment.ObjectMeta, + Spec: extensions.DeploymentSpec{ + Replicas: 100, + }, + Status: extensions.DeploymentStatus{ + Replicas: 100, + }, + } + + if _, _, err := storage.Status.Update(ctx, &update); err != nil { + t.Fatalf("unexpected error: %v", err) + } + response, err := fakeClient.Get(key, false, false) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var deployment extensions.Deployment + testapi.Extensions.Codec().DecodeInto([]byte(response.Node.Value), &deployment) + if deployment.Spec.Replicas != 7 { + t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas) + } + if deployment.Status.Replicas != 100 { + t.Errorf("we expected .status.replicas to be updated to 100 but it was %v", deployment.Status.Replicas) + } +} diff --git a/pkg/registry/deployment/strategy.go b/pkg/registry/deployment/strategy.go index 9a85d225151..19fd5693215 100644 --- a/pkg/registry/deployment/strategy.go +++ b/pkg/registry/deployment/strategy.go @@ -46,6 +46,8 @@ func (deploymentStrategy) NamespaceScoped() bool { // PrepareForCreate clears fields that are not allowed to be set by end users on creation. func (deploymentStrategy) PrepareForCreate(obj runtime.Object) { + deployment := obj.(*extensions.Deployment) + deployment.Status = extensions.DeploymentStatus{} } // Validate validates a new deployment. @@ -61,6 +63,9 @@ func (deploymentStrategy) AllowCreateOnUpdate() bool { // PrepareForUpdate clears fields that are not allowed to be set by end users on update. func (deploymentStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDeployment := obj.(*extensions.Deployment) + oldDeployment := old.(*extensions.Deployment) + newDeployment.Status = oldDeployment.Status } // ValidateUpdate is the default update validation for an end user. @@ -72,6 +77,24 @@ func (deploymentStrategy) AllowUnconditionalUpdate() bool { return true } +type deploymentStatusStrategy struct { + deploymentStrategy +} + +var StatusStrategy = deploymentStatusStrategy{Strategy} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status +func (deploymentStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDeployment := obj.(*extensions.Deployment) + oldDeployment := old.(*extensions.Deployment) + newDeployment.Spec = oldDeployment.Spec +} + +// ValidateUpdate is the default update validation for an end user updating status +func (deploymentStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList { + return validation.ValidateDeploymentUpdate(old.(*extensions.Deployment), obj.(*extensions.Deployment)) +} + // DeploymentToSelectableFields returns a field set that represents the object. func DeploymentToSelectableFields(deployment *extensions.Deployment) fields.Set { return generic.ObjectMetaFieldsSet(deployment.ObjectMeta)