From aead9d3291b502c137567d01af8052a4b8c62497 Mon Sep 17 00:00:00 2001 From: derekwaynecarr Date: Thu, 24 Sep 2015 22:51:09 -0400 Subject: [PATCH] DaemonSets adds a Status subresource --- .../experimental/validation/validation.go | 30 ++++++--- .../validation/validation_test.go | 64 +++++++++++++++++++ pkg/client/unversioned/daemon_sets.go | 8 +++ pkg/client/unversioned/daemon_sets_test.go | 28 ++++++++ .../testclient/fake_daemon_sets.go | 8 +++ pkg/controller/daemon/controller.go | 2 +- pkg/master/master.go | 3 +- pkg/registry/daemonset/etcd/etcd.go | 20 +++++- pkg/registry/daemonset/etcd/etcd_test.go | 19 +++--- pkg/registry/daemonset/strategy.go | 19 ++++++ 10 files changed, 179 insertions(+), 22 deletions(-) diff --git a/pkg/apis/experimental/validation/validation.go b/pkg/apis/experimental/validation/validation.go index 81aa1dfd4f2..f9985209628 100644 --- a/pkg/apis/experimental/validation/validation.go +++ b/pkg/apis/experimental/validation/validation.go @@ -114,6 +114,23 @@ func ValidateDaemonSetUpdate(oldController, controller *experimental.DaemonSet) return allErrs } +// validateDaemonSetStatus validates a DaemonSetStatus +func validateDaemonSetStatus(status *experimental.DaemonSetStatus) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentNumberScheduled), "currentNumberScheduled")...) + allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.NumberMisscheduled), "numberMisscheduled")...) + allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredNumberScheduled), "desiredNumberScheduled")...) + return allErrs +} + +// ValidateDaemonSetStatus validates tests if required fields in the DaemonSet Status section +func ValidateDaemonSetStatusUpdate(controller, oldController *experimental.DaemonSet) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) + allErrs = append(allErrs, validateDaemonSetStatus(&controller.Status)...) + return allErrs +} + // ValidateDaemonSetTemplateUpdate tests that certain fields in the daemon set's pod template are not updated. func ValidateDaemonSetTemplateUpdate(oldPodTemplate, podTemplate *api.PodTemplateSpec) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} @@ -314,16 +331,9 @@ func ValidateJobSpec(spec *experimental.JobSpec) errs.ValidationErrorList { func ValidateJobStatus(status *experimental.JobStatus) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} - - if status.Active < 0 { - allErrs = append(allErrs, errs.NewFieldInvalid("active", status.Active, isNegativeErrorMsg)) - } - if status.Successful < 0 { - allErrs = append(allErrs, errs.NewFieldInvalid("successful", status.Successful, isNegativeErrorMsg)) - } - if status.Unsuccessful < 0 { - allErrs = append(allErrs, errs.NewFieldInvalid("unsuccessful", status.Unsuccessful, isNegativeErrorMsg)) - } + allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Active), "active")...) + allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Successful), "successful")...) + allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Unsuccessful), "unsuccessful")...) return allErrs } diff --git a/pkg/apis/experimental/validation/validation_test.go b/pkg/apis/experimental/validation/validation_test.go index da9fe9e1b06..32d25f48762 100644 --- a/pkg/apis/experimental/validation/validation_test.go +++ b/pkg/apis/experimental/validation/validation_test.go @@ -130,6 +130,70 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { } } +func TestValidateDaemonSetStatusUpdate(t *testing.T) { + type dsUpdateTest struct { + old experimental.DaemonSet + update experimental.DaemonSet + } + + successCases := []dsUpdateTest{ + { + old: experimental.DaemonSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: experimental.DaemonSetStatus{ + CurrentNumberScheduled: 1, + NumberMisscheduled: 2, + DesiredNumberScheduled: 3, + }, + }, + update: experimental.DaemonSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: experimental.DaemonSetStatus{ + CurrentNumberScheduled: 1, + NumberMisscheduled: 1, + DesiredNumberScheduled: 3, + }, + }, + }, + } + + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateDaemonSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]dsUpdateTest{ + "negative values": { + old: experimental.DaemonSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: experimental.DaemonSetStatus{ + CurrentNumberScheduled: 1, + NumberMisscheduled: 2, + DesiredNumberScheduled: 3, + }, + }, + update: experimental.DaemonSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: experimental.DaemonSetStatus{ + CurrentNumberScheduled: -1, + NumberMisscheduled: -1, + DesiredNumberScheduled: -3, + }, + }, + }, + } + + for testName, errorCase := range errorCases { + if errs := ValidateDaemonSetStatusUpdate(&errorCase.old, &errorCase.update); len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + } + } + +} + func TestValidateDaemonSetUpdate(t *testing.T) { validSelector := map[string]string{"a": "b"} validSelector2 := map[string]string{"c": "d"} diff --git a/pkg/client/unversioned/daemon_sets.go b/pkg/client/unversioned/daemon_sets.go index 857ecaf5c94..eb15bf2f2ad 100644 --- a/pkg/client/unversioned/daemon_sets.go +++ b/pkg/client/unversioned/daemon_sets.go @@ -33,6 +33,7 @@ type DaemonSetInterface interface { Get(name string) (*experimental.DaemonSet, error) Create(ctrl *experimental.DaemonSet) (*experimental.DaemonSet, error) Update(ctrl *experimental.DaemonSet) (*experimental.DaemonSet, error) + UpdateStatus(ctrl *experimental.DaemonSet) (*experimental.DaemonSet, error) Delete(name string) error Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) } @@ -77,6 +78,13 @@ func (c *daemonSets) Update(daemon *experimental.DaemonSet) (result *experimenta return } +// UpdateStatus updates an existing daemon set status +func (c *daemonSets) UpdateStatus(daemon *experimental.DaemonSet) (result *experimental.DaemonSet, err error) { + result = &experimental.DaemonSet{} + err = c.r.Put().Namespace(c.ns).Resource("daemonsets").Name(daemon.Name).SubResource("status").Body(daemon).Do().Into(result) + return +} + // Delete deletes an existing daemon set. func (c *daemonSets) Delete(name string) error { return c.r.Delete().Namespace(c.ns).Resource("daemonsets").Name(name).Do().Error() diff --git a/pkg/client/unversioned/daemon_sets_test.go b/pkg/client/unversioned/daemon_sets_test.go index f556f6b689f..b80eca8074a 100644 --- a/pkg/client/unversioned/daemon_sets_test.go +++ b/pkg/client/unversioned/daemon_sets_test.go @@ -122,6 +122,34 @@ func TestUpdateDaemonSet(t *testing.T) { c.Validate(t, receivedDaemonSet, err) } +func TestUpdateDaemonSetUpdateStatus(t *testing.T) { + ns := api.NamespaceDefault + requestDaemonSet := &experimental.DaemonSet{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + } + c := &testClient{ + Request: testRequest{Method: "PUT", Path: testapi.Experimental.ResourcePath(getDSResourceName(), ns, "foo") + "/status", Query: buildQueryValues(nil)}, + Response: Response{ + StatusCode: 200, + Body: &experimental.DaemonSet{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + "name": "baz", + }, + }, + Spec: experimental.DaemonSetSpec{ + Template: &api.PodTemplateSpec{}, + }, + Status: experimental.DaemonSetStatus{}, + }, + }, + } + receivedDaemonSet, err := c.Setup(t).Experimental().DaemonSets(ns).UpdateStatus(requestDaemonSet) + c.Validate(t, receivedDaemonSet, err) +} + func TestDeleteDaemon(t *testing.T) { ns := api.NamespaceDefault c := &testClient{ diff --git a/pkg/client/unversioned/testclient/fake_daemon_sets.go b/pkg/client/unversioned/testclient/fake_daemon_sets.go index 7b04ebd1534..5b40af9ba57 100644 --- a/pkg/client/unversioned/testclient/fake_daemon_sets.go +++ b/pkg/client/unversioned/testclient/fake_daemon_sets.go @@ -66,6 +66,14 @@ func (c *FakeDaemonSets) Update(daemon *experimental.DaemonSet) (*experimental.D return obj.(*experimental.DaemonSet), err } +func (c *FakeDaemonSets) UpdateStatus(daemon *experimental.DaemonSet) (*experimental.DaemonSet, error) { + obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("daemonsets", "status", c.Namespace, daemon), &experimental.DaemonSet{}) + if obj == nil { + return nil, err + } + return obj.(*experimental.DaemonSet), err +} + func (c *FakeDaemonSets) Delete(name string) error { _, err := c.Fake.Invokes(NewDeleteAction("daemonsets", c.Namespace, name), &experimental.DaemonSet{}) return err diff --git a/pkg/controller/daemon/controller.go b/pkg/controller/daemon/controller.go index 39a85f6c4ae..f55307432a3 100644 --- a/pkg/controller/daemon/controller.go +++ b/pkg/controller/daemon/controller.go @@ -400,7 +400,7 @@ func storeDaemonSetStatus(dsClient client.DaemonSetInterface, ds *experimental.D ds.Status.DesiredNumberScheduled = desiredNumberScheduled ds.Status.CurrentNumberScheduled = currentNumberScheduled ds.Status.NumberMisscheduled = numberMisscheduled - _, updateErr = dsClient.Update(ds) + _, updateErr = dsClient.UpdateStatus(ds) if updateErr == nil { // successful update return nil diff --git a/pkg/master/master.go b/pkg/master/master.go index 6dcda9f3d0c..8a8d327ecfb 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -958,7 +958,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion { controllerStorage := expcontrolleretcd.NewStorage(c.ExpDatabaseStorage) autoscalerStorage := horizontalpodautoscaleretcd.NewREST(c.ExpDatabaseStorage) thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(c.ExpDatabaseStorage) - daemonSetStorage := daemonetcd.NewREST(c.ExpDatabaseStorage) + daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(c.ExpDatabaseStorage) deploymentStorage := deploymentetcd.NewStorage(c.ExpDatabaseStorage) jobStorage, jobStatusStorage := jobetcd.NewREST(c.ExpDatabaseStorage) @@ -979,6 +979,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion { 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, diff --git a/pkg/registry/daemonset/etcd/etcd.go b/pkg/registry/daemonset/etcd/etcd.go index d126968a2b1..8811be623fa 100644 --- a/pkg/registry/daemonset/etcd/etcd.go +++ b/pkg/registry/daemonset/etcd/etcd.go @@ -37,7 +37,7 @@ type REST struct { var daemonPrefix = "/daemonsets" // NewREST returns a RESTStorage object that will work against DaemonSets. -func NewREST(s storage.Interface) *REST { +func NewREST(s storage.Interface) (*REST, *StatusREST) { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &experimental.DaemonSet{} }, @@ -71,6 +71,22 @@ func NewREST(s storage.Interface) *REST { Storage: s, } + statusStore := *store + statusStore.UpdateStrategy = daemonset.StatusStrategy - return &REST{store} + 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 &experimental.DaemonSet{} +} + +// 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) } diff --git a/pkg/registry/daemonset/etcd/etcd_test.go b/pkg/registry/daemonset/etcd/etcd_test.go index 5e3059d0a8a..798cace9fc3 100755 --- a/pkg/registry/daemonset/etcd/etcd_test.go +++ b/pkg/registry/daemonset/etcd/etcd_test.go @@ -28,9 +28,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, "experimental") - return NewREST(etcdStorage), fakeClient + storage, statusStorage := NewREST(etcdStorage) + return storage, statusStorage, fakeClient } func newValidDaemonSet() *experimental.DaemonSet { @@ -64,7 +65,7 @@ func newValidDaemonSet() *experimental.DaemonSet { var validDaemonSet = newValidDaemonSet() func TestCreate(t *testing.T) { - storage, fakeClient := newStorage(t) + storage, _, fakeClient := newStorage(t) test := registrytest.New(t, fakeClient, storage.Etcd) ds := newValidDaemonSet() ds.ObjectMeta = api.ObjectMeta{} @@ -82,7 +83,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 @@ -118,25 +119,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(newValidDaemonSet()) } func TestGet(t *testing.T) { - storage, fakeClient := newStorage(t) + storage, _, fakeClient := newStorage(t) test := registrytest.New(t, fakeClient, storage.Etcd) test.TestGet(newValidDaemonSet()) } func TestList(t *testing.T) { - storage, fakeClient := newStorage(t) + storage, _, fakeClient := newStorage(t) test := registrytest.New(t, fakeClient, storage.Etcd) test.TestList(newValidDaemonSet()) } func TestWatch(t *testing.T) { - storage, fakeClient := newStorage(t) + storage, _, fakeClient := newStorage(t) test := registrytest.New(t, fakeClient, storage.Etcd) test.TestWatch( validDaemonSet, @@ -160,3 +161,5 @@ func TestWatch(t *testing.T) { }, ) } + +// TODO TestUpdateStatus diff --git a/pkg/registry/daemonset/strategy.go b/pkg/registry/daemonset/strategy.go index 7369afe51ff..924ecadd3c9 100644 --- a/pkg/registry/daemonset/strategy.go +++ b/pkg/registry/daemonset/strategy.go @@ -57,6 +57,9 @@ func (daemonSetStrategy) PrepareForUpdate(obj, old runtime.Object) { newDaemonSet := obj.(*experimental.DaemonSet) oldDaemonSet := old.(*experimental.DaemonSet) + // update is not allowed to set status + newDaemonSet.Status = oldDaemonSet.Status + // Any changes to the spec increment the generation number, any changes to the // status should reflect the generation number of the corresponding object. We push // the burden of managing the status onto the clients because we can't (in general) @@ -120,3 +123,19 @@ func MatchDaemonSet(label labels.Selector, field fields.Selector) generic.Matche }, } } + +type daemonSetStatusStrategy struct { + daemonSetStrategy +} + +var StatusStrategy = daemonSetStatusStrategy{Strategy} + +func (daemonSetStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDaemonSet := obj.(*experimental.DaemonSet) + oldDaemonSet := old.(*experimental.DaemonSet) + newDaemonSet.Spec = oldDaemonSet.Spec +} + +func (daemonSetStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList { + return validation.ValidateDaemonSetStatusUpdate(obj.(*experimental.DaemonSet), old.(*experimental.DaemonSet)) +}