DaemonSets adds a Status subresource

This commit is contained in:
derekwaynecarr 2015-09-24 22:51:09 -04:00
parent 6a04145362
commit aead9d3291
10 changed files with 179 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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