diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 04aabc7c814..44e00be24be 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -666,6 +666,23 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { return allErrs } +// ValidatePodStatusUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields +// that cannot be changed. +func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + + allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta).Prefix("metadata")...) + + // TODO: allow change when bindings are properly decoupled from pods + if newPod.Status.Host != oldPod.Status.Host { + allErrs = append(allErrs, errs.NewFieldInvalid("status.host", newPod.Status.Host, "pod host cannot be changed directly")) + } + + newPod.Spec = oldPod.Spec + + return allErrs +} + var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone)) // ValidateService tests if required fields in the service are set. diff --git a/pkg/apiserver/api_installer.go b/pkg/apiserver/api_installer.go index e1854ae012f..9ccb315aa54 100644 --- a/pkg/apiserver/api_installer.go +++ b/pkg/apiserver/api_installer.go @@ -88,7 +88,16 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage codec := a.group.codec admit := a.group.admit context := a.group.context - resource := path + + var resource, subresource string + switch parts := strings.Split(path, "/"); len(parts) { + case 2: + resource, subresource = parts[0], parts[1] + case 1: + resource = parts[0] + default: + return fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)") + } object := storage.New() // TODO: add scheme to APIInstaller rather than using api.Scheme @@ -143,14 +152,17 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage // Get the list of actions for the given scope. if scope.Name() != meta.RESTScopeNameNamespace { - itemPath := path + "/{name}" + itemPath := resource + "/{name}" + if len(subresource) > 0 { + itemPath = itemPath + "/" + subresource + } nameParams := append(params, nameParam) namer := rootScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)} // Handler for standard REST verbs (GET, PUT, POST and DELETE). - actions = appendIf(actions, action{"LIST", path, params, namer}, isLister) - actions = appendIf(actions, action{"POST", path, params, namer}, isCreater) - actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList) + actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister) + actions = appendIf(actions, action{"POST", resource, params, namer}, isCreater) + actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) @@ -165,10 +177,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage if scope.ParamPath() { // Handler for standard REST verbs (GET, PUT, POST and DELETE). namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string") - namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path + namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + resource namespaceParams := []*restful.Parameter{namespaceParam} itemPath := namespacedPath + "/{name}" + if len(subresource) > 0 { + itemPath = itemPath + "/" + subresource + } nameParams := append(namespaceParams, nameParam) namer := scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), false} @@ -186,8 +201,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage // list across namespace. namer = scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), true} - actions = appendIf(actions, action{"LIST", path, params, namer}, isLister) - actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList) + actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister) + actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList) } else { // Handler for standard REST verbs (GET, PUT, POST and DELETE). @@ -195,13 +210,16 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string") namespaceParams := []*restful.Parameter{namespaceParam} - itemPath := path + "/{name}" + itemPath := resource + "/{name}" + if len(subresource) > 0 { + itemPath = itemPath + "/" + subresource + } nameParams := append(namespaceParams, nameParam) namer := legacyScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)} - actions = appendIf(actions, action{"LIST", path, namespaceParams, namer}, isLister) - actions = appendIf(actions, action{"POST", path, namespaceParams, namer}, isCreater) - actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams, namer}, allowWatchList) + actions = appendIf(actions, action{"LIST", resource, namespaceParams, namer}, isLister) + actions = appendIf(actions, action{"POST", resource, namespaceParams, namer}, isCreater) + actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, namespaceParams, namer}, allowWatchList) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) diff --git a/pkg/master/master.go b/pkg/master/master.go index 946976539ac..6dc999ccb6d 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -372,7 +372,7 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) func (m *Master) init(c *Config) { boundPodFactory := &pod.BasicBoundPodFactory{} - podStorage, bindingStorage := podetcd.NewREST(c.EtcdHelper, boundPodFactory) + podStorage, bindingStorage, podStatusStorage := podetcd.NewREST(c.EtcdHelper, boundPodFactory) podRegistry := pod.NewRegistry(podStorage) eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())) @@ -406,8 +406,9 @@ func (m *Master) init(c *Config) { // TODO: Factor out the core API registration m.storage = map[string]apiserver.RESTStorage{ - "pods": podStorage, - "bindings": bindingStorage, + "pods": podStorage, + "pods/status": podStatusStorage, + "bindings": bindingStorage, "replicationControllers": controller.NewREST(registry, podRegistry), "services": service.NewREST(m.serviceRegistry, c.Cloud, m.nodeRegistry, m.portalNet, c.ClusterName), diff --git a/pkg/registry/etcd/etcd_test.go b/pkg/registry/etcd/etcd_test.go index 2384fb76a3f..0990bad0057 100644 --- a/pkg/registry/etcd/etcd_test.go +++ b/pkg/registry/etcd/etcd_test.go @@ -41,7 +41,7 @@ func NewTestEtcdRegistry(client tools.EtcdClient) *Registry { func NewTestEtcdRegistryWithPods(client tools.EtcdClient) *Registry { helper := tools.EtcdHelper{client, latest.Codec, tools.RuntimeVersionAdapter{latest.ResourceVersioner}} - podStorage, _ := podetcd.NewREST(helper, nil) + podStorage, _, _ := podetcd.NewREST(helper, nil) registry := NewRegistry(helper, pod.NewRegistry(podStorage)) return registry } diff --git a/pkg/registry/pod/etcd/etcd.go b/pkg/registry/pod/etcd/etcd.go index 6c7f1b96e04..9bedb080854 100644 --- a/pkg/registry/pod/etcd/etcd.go +++ b/pkg/registry/pod/etcd/etcd.go @@ -39,9 +39,8 @@ type REST struct { } // NewREST returns a RESTStorage object that will work against pods. -func NewREST(h tools.EtcdHelper, factory pod.BoundPodFactory) (*REST, *BindingREST) { +func NewREST(h tools.EtcdHelper, factory pod.BoundPodFactory) (*REST, *BindingREST, *StatusREST) { prefix := "/registry/pods" - bindings := &podLifecycle{h} store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Pod{} }, NewListFunc: func() runtime.Object { return &api.PodList{} }, @@ -56,17 +55,20 @@ func NewREST(h tools.EtcdHelper, factory pod.BoundPodFactory) (*REST, *BindingRE }, EndpointName: "pods", - CreateStrategy: pod.Strategy, - - UpdateStrategy: pod.Strategy, - AfterUpdate: bindings.AfterUpdate, - - ReturnDeletedObject: true, - AfterDelete: bindings.AfterDelete, - Helper: h, } - return &REST{store: store}, &BindingREST{store: store, factory: factory} + statusStore := *store + + bindings := &podLifecycle{h} + store.CreateStrategy = pod.Strategy + store.UpdateStrategy = pod.Strategy + store.AfterUpdate = bindings.AfterUpdate + store.ReturnDeletedObject = true + store.AfterDelete = bindings.AfterDelete + + statusStore.UpdateStrategy = pod.StatusStrategy + + return &REST{store: store}, &BindingREST{store: store, factory: factory}, &StatusREST{store: &statusStore} } // WithPodStatus returns a rest object that decorates returned responses with extra @@ -250,3 +252,17 @@ func (h *podLifecycle) AfterDelete(obj runtime.Object) error { return pods, nil }) } + +// StatusREST implements the REST endpoint for changing the status of a pod. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.Pod{} +} + +// 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/pod/etcd/etcd_test.go b/pkg/registry/pod/etcd/etcd_test.go index 449d99d9bf0..2e49e129e36 100644 --- a/pkg/registry/pod/etcd/etcd_test.go +++ b/pkg/registry/pod/etcd/etcd_test.go @@ -65,11 +65,11 @@ func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) { return fakeEtcdClient, helper } -func newStorage(t *testing.T) (*REST, *BindingREST, *tools.FakeEtcdClient, tools.EtcdHelper) { +func newStorage(t *testing.T) (*REST, *BindingREST, *StatusREST, *tools.FakeEtcdClient, tools.EtcdHelper) { fakeEtcdClient, h := newHelper(t) - storage, bindingStorage := NewREST(h, &pod.BasicBoundPodFactory{}) + storage, bindingStorage, statusStorage := NewREST(h, &pod.BasicBoundPodFactory{}) storage = storage.WithPodStatus(&fakeCache{statusToReturn: &api.PodStatus{}}) - return storage, bindingStorage, fakeEtcdClient, h + return storage, bindingStorage, statusStorage, fakeEtcdClient, h } func validNewPod() *api.Pod { @@ -104,13 +104,13 @@ func validChangedPod() *api.Pod { } func TestStorage(t *testing.T) { - storage, _, _, _ := newStorage(t) + storage, _, _, _, _ := newStorage(t) pod.NewRegistry(storage) } func TestCreate(t *testing.T) { fakeEtcdClient, helper := newHelper(t) - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{}} storage = storage.WithPodStatus(cache) test := resttest.New(t, storage, fakeEtcdClient.SetError) @@ -140,7 +140,7 @@ func expectPod(t *testing.T, out runtime.Object) (*api.Pod, bool) { func TestCreateRegistryError(t *testing.T) { fakeEtcdClient, helper := newHelper(t) fakeEtcdClient.Err = fmt.Errorf("test error") - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) pod := validNewPod() _, err := storage.Create(api.NewDefaultContext(), pod) @@ -151,7 +151,7 @@ func TestCreateRegistryError(t *testing.T) { func TestCreateSetsFields(t *testing.T) { fakeEtcdClient, helper := newHelper(t) - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{}} storage = storage.WithPodStatus(cache) pod := validNewPod() @@ -175,7 +175,7 @@ func TestCreateSetsFields(t *testing.T) { func TestListError(t *testing.T) { fakeEtcdClient, helper := newHelper(t) fakeEtcdClient.Err = fmt.Errorf("test error") - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{} storage = storage.WithPodStatus(cache) pods, err := storage.List(api.NewDefaultContext(), labels.Everything(), labels.Everything()) @@ -203,7 +203,7 @@ func TestListCacheError(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{errorToReturn: client.ErrPodInfoNotAvailable} storage = storage.WithPodStatus(cache) @@ -228,7 +228,7 @@ func TestListEmptyPodList(t *testing.T) { E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound), } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{} storage = storage.WithPodStatus(cache) pods, err := storage.List(api.NewContext(), labels.Everything(), labels.Everything()) @@ -266,7 +266,7 @@ func TestListPodList(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{Phase: api.PodRunning}} storage = storage.WithPodStatus(cache) @@ -317,7 +317,7 @@ func TestListPodListSelection(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{Phase: api.PodRunning}} storage = storage.WithPodStatus(cache) @@ -384,7 +384,7 @@ func TestListPodListSelection(t *testing.T) { } func TestPodDecode(t *testing.T) { - storage, _ := NewREST(tools.EtcdHelper{}, nil) + storage, _, _ := NewREST(tools.EtcdHelper{}, nil) expected := validNewPod() body, err := latest.Codec.Encode(expected) if err != nil { @@ -413,7 +413,7 @@ func TestGet(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{Phase: api.PodRunning}} storage = storage.WithPodStatus(cache) @@ -441,7 +441,7 @@ func TestGetCacheError(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{errorToReturn: client.ErrPodInfoNotAvailable} storage = storage.WithPodStatus(cache) @@ -461,7 +461,7 @@ func TestGetCacheError(t *testing.T) { func TestPodStorageValidatesCreate(t *testing.T) { fakeEtcdClient, helper := newHelper(t) fakeEtcdClient.Err = fmt.Errorf("test error") - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{}} storage = storage.WithPodStatus(cache) @@ -481,7 +481,7 @@ func TestPodStorageValidatesCreate(t *testing.T) { // TODO: remove, this is covered by RESTTest.TestCreate func TestCreatePod(t *testing.T) { _, helper := newHelper(t) - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{}} storage = storage.WithPodStatus(cache) @@ -505,7 +505,7 @@ func TestCreatePod(t *testing.T) { // TODO: remove, this is covered by RESTTest.TestCreate func TestCreateWithConflictingNamespace(t *testing.T) { _, helper := newHelper(t) - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{} storage = storage.WithPodStatus(cache) @@ -536,7 +536,7 @@ func TestUpdateWithConflictingNamespace(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{} storage = storage.WithPodStatus(cache) @@ -648,7 +648,7 @@ func TestResourceLocation(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{PodIP: expectedIP}} storage = storage.WithPodStatus(cache) @@ -706,7 +706,7 @@ func TestDeletePod(t *testing.T) { }, }, } - storage, _ := NewREST(helper, nil) + storage, _, _ := NewREST(helper, nil) cache := &fakeCache{statusToReturn: &api.PodStatus{}} storage = storage.WithPodStatus(cache) @@ -730,7 +730,7 @@ func TestDeletePod(t *testing.T) { // TestEtcdGetDifferentNamespace ensures same-name pods in different namespaces do not clash func TestEtcdGetDifferentNamespace(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx1 := api.NewDefaultContext() ctx2 := api.WithNamespace(api.NewContext(), "other") @@ -768,7 +768,7 @@ func TestEtcdGetDifferentNamespace(t *testing.T) { } func TestEtcdGet(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() key, _ := registry.store.KeyFunc(ctx, "foo") fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0) @@ -783,7 +783,7 @@ func TestEtcdGet(t *testing.T) { } func TestEtcdGetNotFound(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() key, _ := registry.store.KeyFunc(ctx, "foo") fakeClient.Data[key] = tools.EtcdResponseWithError{ @@ -799,7 +799,7 @@ func TestEtcdGetNotFound(t *testing.T) { } func TestEtcdCreate(t *testing.T) { - registry, bindingRegistry, fakeClient, _ := newStorage(t) + registry, bindingRegistry, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true key, _ := registry.store.KeyFunc(ctx, "foo") @@ -847,7 +847,7 @@ func TestEtcdCreate(t *testing.T) { } func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) fakeClient.TestIndex = true pod := validNewPod() pod.Namespace = "" @@ -859,7 +859,7 @@ func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { } func TestEtcdCreateAlreadyExisting(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() key, _ := registry.store.KeyFunc(ctx, "foo") fakeClient.Data[key] = tools.EtcdResponseWithError{ @@ -877,7 +877,7 @@ func TestEtcdCreateAlreadyExisting(t *testing.T) { } func TestEtcdCreateWithContainersError(t *testing.T) { - registry, bindingRegistry, fakeClient, _ := newStorage(t) + registry, bindingRegistry, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true key, _ := registry.store.KeyFunc(ctx, "foo") @@ -915,7 +915,7 @@ func TestEtcdCreateWithContainersError(t *testing.T) { } func TestEtcdCreateWithContainersNotFound(t *testing.T) { - registry, bindingRegistry, fakeClient, _ := newStorage(t) + registry, bindingRegistry, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true key, _ := registry.store.KeyFunc(ctx, "foo") @@ -968,7 +968,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) { } func TestEtcdCreateWithExistingContainers(t *testing.T) { - registry, bindingRegistry, fakeClient, _ := newStorage(t) + registry, bindingRegistry, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true key, _ := registry.store.KeyFunc(ctx, "foo") @@ -1020,7 +1020,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { } func TestEtcdUpdateNotFound(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true @@ -1046,7 +1046,7 @@ func TestEtcdUpdateNotFound(t *testing.T) { } func TestEtcdUpdateNotScheduled(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true @@ -1070,7 +1070,7 @@ func TestEtcdUpdateNotScheduled(t *testing.T) { } func TestEtcdUpdateScheduled(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true @@ -1175,8 +1175,80 @@ func TestEtcdUpdateScheduled(t *testing.T) { } } +func TestEtcdUpdateStatus(t *testing.T) { + registry, _, status, fakeClient, helper := newStorage(t) + ctx := api.NewDefaultContext() + fakeClient.TestIndex = true + + key, _ := registry.store.KeyFunc(ctx, "foo") + podStart := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo:v1", + }, + }, + }, + Status: api.PodStatus{ + Host: "machine", + }, + } + fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &podStart), 1) + + podIn := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + ResourceVersion: "1", + Labels: map[string]string{ + "foo": "bar", + }, + }, + // should be ignored + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo:v2", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePath: api.TerminationMessagePathDefault, + }, + }, + }, + Status: api.PodStatus{ + Host: "machine", + Phase: api.PodRunning, + PodIP: "127.0.0.1", + Message: "is now scheduled", + }, + } + + expected := podStart + expected.ResourceVersion = "2" + expected.Spec.RestartPolicy.Always = &api.RestartPolicyAlways{} + expected.Spec.DNSPolicy = api.DNSClusterFirst + expected.Spec.Containers[0].ImagePullPolicy = api.PullIfNotPresent + expected.Spec.Containers[0].TerminationMessagePath = api.TerminationMessagePathDefault + expected.Labels = podIn.Labels + expected.Status = podIn.Status + + _, _, err := status.Update(ctx, &podIn) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + var podOut api.Pod + if err := helper.ExtractObj(key, &podOut, false); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !api.Semantic.DeepEqual(expected, podOut) { + t.Errorf("unexpected object: %s", util.ObjectDiff(expected, podOut)) + } +} + func TestEtcdDeletePod(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true @@ -1212,7 +1284,7 @@ func TestEtcdDeletePod(t *testing.T) { } func TestEtcdDeletePodMultipleContainers(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true key, _ := registry.store.KeyFunc(ctx, "foo") @@ -1252,7 +1324,7 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) { } func TestEtcdEmptyList(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() key := registry.store.KeyRootFunc(ctx) fakeClient.Data[key] = tools.EtcdResponseWithError{ @@ -1275,7 +1347,7 @@ func TestEtcdEmptyList(t *testing.T) { } func TestEtcdListNotFound(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() key := registry.store.KeyRootFunc(ctx) fakeClient.Data[key] = tools.EtcdResponseWithError{ @@ -1293,7 +1365,7 @@ func TestEtcdListNotFound(t *testing.T) { } func TestEtcdList(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() key := registry.store.KeyRootFunc(ctx) fakeClient.Data[key] = tools.EtcdResponseWithError{ @@ -1333,7 +1405,7 @@ func TestEtcdList(t *testing.T) { } func TestEtcdWatchPods(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() watching, err := registry.Watch(ctx, labels.Everything(), @@ -1360,7 +1432,7 @@ func TestEtcdWatchPods(t *testing.T) { } func TestEtcdWatchPodsMatch(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() watching, err := registry.Watch(ctx, labels.SelectorFromSet(labels.Set{"name": "foo"}), @@ -1399,7 +1471,7 @@ func TestEtcdWatchPodsMatch(t *testing.T) { } func TestEtcdWatchPodsNotMatch(t *testing.T) { - registry, _, fakeClient, _ := newStorage(t) + registry, _, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() watching, err := registry.Watch(ctx, labels.SelectorFromSet(labels.Set{"name": "foo"}), diff --git a/pkg/registry/pod/rest.go b/pkg/registry/pod/rest.go index 1172041cfe7..91e0b8bace8 100644 --- a/pkg/registry/pod/rest.go +++ b/pkg/registry/pod/rest.go @@ -70,6 +70,17 @@ func (podStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErro return validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod)) } +type podStatusStrategy struct { + podStrategy +} + +var StatusStrategy = podStatusStrategy{Strategy} + +func (podStatusStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErrorList { + // TODO: merge valid fields after update + return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod)) +} + // PodStatusGetter is an interface used by Pods to fetch and retrieve status info. type PodStatusGetter interface { GetPodStatus(namespace, name string) (*api.PodStatus, error)