Merge pull request #14672 from derekwaynecarr/rc_status

Add replication controller status subresource
pull/6/head
Piotr Szczesniak 2015-10-08 13:30:29 +02:00
commit 6217869085
13 changed files with 263 additions and 28 deletions

View File

@ -8024,6 +8024,65 @@
}
]
},
{
"path": "/api/v1/namespaces/{namespace}/replicationcontrollers/{name}/status",
"description": "API at /api/v1",
"operations": [
{
"type": "v1.ReplicationController",
"method": "PUT",
"summary": "replace status of the specified ReplicationController",
"nickname": "replaceNamespacedReplicationControllerStatus",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "v1.ReplicationController",
"paramType": "body",
"name": "body",
"description": "",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the ReplicationController",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.ReplicationController"
}
],
"produces": [
"application/json"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1/namespaces/{namespace}/resourcequotas",
"description": "API at /api/v1",

View File

@ -1352,6 +1352,15 @@ func ValidateReplicationControllerUpdate(oldController, controller *api.Replicat
return allErrs
}
// ValidateReplicationControllerStatusUpdate tests if required fields in the replication controller are set.
func ValidateReplicationControllerStatusUpdate(oldController, controller *api.ReplicationController) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...)
allErrs = append(allErrs, ValidatePositiveField(int64(controller.Status.Replicas), "status.replicas")...)
allErrs = append(allErrs, ValidatePositiveField(int64(controller.Status.ObservedGeneration), "status.observedGeneration")...)
return allErrs
}
// Validates that the given selector is non-empty.
func ValidateNonEmptySelector(selectorMap map[string]string, fieldName string) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}

View File

@ -2101,6 +2101,89 @@ func TestValidateService(t *testing.T) {
}
}
func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
type rcUpdateTest struct {
old api.ReplicationController
update api.ReplicationController
}
successCases := []rcUpdateTest{
{
old: api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Template,
},
Status: api.ReplicationControllerStatus{
Replicas: 2,
},
},
update: api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Replicas: 3,
Selector: validSelector,
Template: &validPodTemplate.Template,
},
Status: api.ReplicationControllerStatus{
Replicas: 4,
},
},
},
}
for _, successCase := range successCases {
successCase.old.ObjectMeta.ResourceVersion = "1"
successCase.update.ObjectMeta.ResourceVersion = "1"
if errs := ValidateReplicationControllerStatusUpdate(&successCase.old, &successCase.update); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := map[string]rcUpdateTest{
"negative replicas": {
old: api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Template,
},
Status: api.ReplicationControllerStatus{
Replicas: 3,
},
},
update: api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: validSelector,
Template: &validPodTemplate.Template,
},
Status: api.ReplicationControllerStatus{
Replicas: -3,
},
},
},
}
for testName, errorCase := range errorCases {
if errs := ValidateReplicationControllerStatusUpdate(&errorCase.old, &errorCase.update); len(errs) == 0 {
t.Errorf("expected failure: %s", testName)
}
}
}
func TestValidateReplicationControllerUpdate(t *testing.T) {
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
@ -2475,7 +2558,8 @@ func TestValidateReplicationController(t *testing.T) {
field != "spec.replicas" &&
field != "spec.template.labels" &&
field != "metadata.annotations" &&
field != "metadata.labels" {
field != "metadata.labels" &&
field != "status.replicas" {
t.Errorf("%s: missing prefix for: %v", k, errs[i])
}
}

View File

@ -34,6 +34,7 @@ type ReplicationControllerInterface interface {
Get(name string) (*api.ReplicationController, error)
Create(ctrl *api.ReplicationController) (*api.ReplicationController, error)
Update(ctrl *api.ReplicationController) (*api.ReplicationController, error)
UpdateStatus(ctrl *api.ReplicationController) (*api.ReplicationController, error)
Delete(name string) error
Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
}
@ -77,6 +78,13 @@ func (c *replicationControllers) Update(controller *api.ReplicationController) (
return
}
// UpdateStatus updates an existing replication controller status
func (c *replicationControllers) UpdateStatus(controller *api.ReplicationController) (result *api.ReplicationController, err error) {
result = &api.ReplicationController{}
err = c.r.Put().Namespace(c.ns).Resource("replicationControllers").Name(controller.Name).SubResource("status").Body(controller).Do().Into(result)
return
}
// Delete deletes an existing replication controller.
func (c *replicationControllers) Delete(name string) error {
return c.r.Delete().Namespace(c.ns).Resource("replicationControllers").Name(name).Do().Error()

View File

@ -124,6 +124,36 @@ func TestUpdateController(t *testing.T) {
c.Validate(t, receivedController, err)
}
func TestUpdateStatusController(t *testing.T) {
ns := api.NamespaceDefault
requestController := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
}
c := &testClient{
Request: testRequest{Method: "PUT", Path: testapi.Default.ResourcePath(getRCResourceName(), ns, "foo") + "/status", Query: buildQueryValues(nil)},
Response: Response{
StatusCode: 200,
Body: &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Template: &api.PodTemplateSpec{},
},
Status: api.ReplicationControllerStatus{
Replicas: 2,
},
},
},
}
receivedController, err := c.Setup(t).ReplicationControllers(ns).UpdateStatus(requestController)
c.Validate(t, receivedController, err)
}
func TestDeleteController(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{

View File

@ -66,6 +66,14 @@ func (c *FakeReplicationControllers) Update(controller *api.ReplicationControlle
return obj.(*api.ReplicationController), err
}
func (c *FakeReplicationControllers) UpdateStatus(controller *api.ReplicationController) (*api.ReplicationController, error) {
obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("replicationcontrollers", "status", c.Namespace, controller), &api.ReplicationController{})
if obj == nil {
return nil, err
}
return obj.(*api.ReplicationController), err
}
func (c *FakeReplicationControllers) Delete(name string) error {
_, err := c.Fake.Invokes(NewDeleteAction("replicationcontrollers", c.Namespace, name), &api.ReplicationController{})
return err

View File

@ -250,7 +250,7 @@ func TestStatusUpdatesWithoutReplicasChange(t *testing.T) {
rc.Status.ObservedGeneration = rc.Generation
updatedRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc)
fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name), "PUT", &updatedRc)
fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &updatedRc)
}
func TestControllerUpdateReplicas(t *testing.T) {
@ -288,7 +288,7 @@ func TestControllerUpdateReplicas(t *testing.T) {
rc.Status = api.ReplicationControllerStatus{Replicas: 4, ObservedGeneration: 1}
decRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc)
fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name), "PUT", &decRc)
fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &decRc)
validateSyncReplication(t, &fakePodControl, 1, 0)
}

View File

@ -43,7 +43,7 @@ func updateReplicaCount(rcClient client.ReplicationControllerInterface, controll
controller.Name, controller.Status.Replicas, numReplicas, controller.Spec.Replicas, controller.Status.ObservedGeneration, generation)
rc.Status = api.ReplicationControllerStatus{Replicas: numReplicas, ObservedGeneration: generation}
_, updateErr = rcClient.Update(rc)
_, updateErr = rcClient.UpdateStatus(rc)
if updateErr == nil || i >= statusUpdateRetries {
return updateErr
}

View File

@ -551,7 +551,7 @@ func (m *Master) init(c *Config) {
})
m.serviceNodePortAllocator = serviceNodePortRegistry
controllerStorage := controlleretcd.NewREST(dbClient("replicationControllers"))
controllerStorage, controllerStatusStorage := controlleretcd.NewREST(dbClient("replicationControllers"))
// TODO: Factor out the core API registration
m.storage = map[string]rest.Storage{
@ -567,12 +567,13 @@ func (m *Master) init(c *Config) {
"podTemplates": podTemplateStorage,
"replicationControllers": controllerStorage,
"services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator),
"endpoints": endpointsStorage,
"nodes": nodeStorage,
"nodes/status": nodeStatusStorage,
"events": eventStorage,
"replicationControllers": controllerStorage,
"replicationControllers/status": controllerStatusStorage,
"services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator),
"endpoints": endpointsStorage,
"nodes": nodeStorage,
"nodes/status": nodeStatusStorage,
"events": eventStorage,
"limitRanges": limitRangeStorage,
"resourceQuotas": resourceQuotaStorage,

View File

@ -32,7 +32,7 @@ type REST struct {
}
// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(s storage.Interface) *REST {
func NewREST(s storage.Interface) (*REST, *StatusREST) {
prefix := "/controllers"
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.ReplicationController{} },
@ -67,5 +67,22 @@ func NewREST(s storage.Interface) *REST {
Storage: s,
}
return &REST{store}
statusStore := *store
statusStore.UpdateStrategy = controller.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}
}
// StatusREST implements the REST endpoint for changing the status of a replication controller
type StatusREST struct {
store *etcdgeneric.Etcd
}
func (r *StatusREST) New() runtime.Object {
return &api.ReplicationController{}
}
// 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

@ -27,9 +27,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, "")
return NewREST(etcdStorage), fakeClient
storage, statusStorage := NewREST(etcdStorage)
return storage, statusStorage, fakeClient
}
// createController is a helper function that returns a controller with the updated resource version.
@ -74,7 +75,7 @@ func validNewController() *api.ReplicationController {
var validController = validNewController()
func TestCreate(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
controller := validNewController()
controller.ObjectMeta = api.ObjectMeta{}
@ -93,7 +94,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
@ -124,13 +125,13 @@ 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(validNewController())
}
func TestGenerationNumber(t *testing.T) {
storage, _ := newStorage(t)
storage, _, _ := newStorage(t)
modifiedSno := *validNewController()
modifiedSno.Generation = 100
modifiedSno.Status.ObservedGeneration = 10
@ -179,19 +180,19 @@ func TestGenerationNumber(t *testing.T) {
}
func TestGet(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestGet(validNewController())
}
func TestList(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestList(validNewController())
}
func TestWatch(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestWatch(
validController,
@ -220,3 +221,5 @@ func TestWatch(t *testing.T) {
},
)
}
//TODO TestUpdateStatus

View File

@ -50,17 +50,14 @@ func (rcStrategy) PrepareForCreate(obj runtime.Object) {
controller.Status = api.ReplicationControllerStatus{}
controller.Generation = 1
controller.Status.ObservedGeneration = 0
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (rcStrategy) PrepareForUpdate(obj, old runtime.Object) {
// TODO: once RC has a status sub-resource we can enable this.
//newController := obj.(*api.ReplicationController)
//oldController := old.(*api.ReplicationController)
//newController.Status = oldController.Status
newController := obj.(*api.ReplicationController)
oldController := old.(*api.ReplicationController)
// update is not allowed to set status
newController.Status = oldController.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
@ -125,3 +122,20 @@ func MatchController(label labels.Selector, field fields.Selector) generic.Match
},
}
}
type rcStatusStrategy struct {
rcStrategy
}
var StatusStrategy = rcStatusStrategy{Strategy}
func (rcStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
newRc := obj.(*api.ReplicationController)
oldRc := old.(*api.ReplicationController)
// update is not allowed to set spec
newRc.Spec = oldRc.Spec
}
func (rcStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateReplicationControllerStatusUpdate(old.(*api.ReplicationController), obj.(*api.ReplicationController))
}

View File

@ -38,7 +38,9 @@ type ContainerStorage struct {
}
func NewStorage(s storage.Interface) ContainerStorage {
rcRegistry := controller.NewRegistry(etcd.NewREST(s))
// scale does not set status, only updates spec so we ignore the status
controllerREST, _ := etcd.NewREST(s)
rcRegistry := controller.NewRegistry(controllerREST)
return ContainerStorage{
ReplicationController: &RcREST{},