Merge pull request #4779 from smarterclayton/status_endpoints

Minimal status mutation change
This commit is contained in:
Brian Grant 2015-03-03 11:00:02 -08:00
commit fca9fd68c7
7 changed files with 203 additions and 68 deletions

View File

@ -666,6 +666,23 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList {
return allErrs 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)) var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone))
// ValidateService tests if required fields in the service are set. // ValidateService tests if required fields in the service are set.

View File

@ -88,7 +88,16 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
codec := a.group.codec codec := a.group.codec
admit := a.group.admit admit := a.group.admit
context := a.group.context 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() object := storage.New()
// TODO: add scheme to APIInstaller rather than using api.Scheme // 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. // Get the list of actions for the given scope.
if scope.Name() != meta.RESTScopeNameNamespace { if scope.Name() != meta.RESTScopeNameNamespace {
itemPath := path + "/{name}" itemPath := resource + "/{name}"
if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource
}
nameParams := append(params, nameParam) nameParams := append(params, nameParam)
namer := rootScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)} namer := rootScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)}
// Handler for standard REST verbs (GET, PUT, POST and DELETE). // Handler for standard REST verbs (GET, PUT, POST and DELETE).
actions = appendIf(actions, action{"LIST", path, params, namer}, isLister) actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister)
actions = appendIf(actions, action{"POST", path, params, namer}, isCreater) actions = appendIf(actions, action{"POST", resource, params, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
@ -165,10 +177,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
if scope.ParamPath() { if scope.ParamPath() {
// Handler for standard REST verbs (GET, PUT, POST and DELETE). // Handler for standard REST verbs (GET, PUT, POST and DELETE).
namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string") 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} namespaceParams := []*restful.Parameter{namespaceParam}
itemPath := namespacedPath + "/{name}" itemPath := namespacedPath + "/{name}"
if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource
}
nameParams := append(namespaceParams, nameParam) nameParams := append(namespaceParams, nameParam)
namer := scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), false} 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. // list across namespace.
namer = scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), true} 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{"LIST", resource, params, namer}, isLister)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList)
} else { } else {
// Handler for standard REST verbs (GET, PUT, POST and DELETE). // 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") namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
namespaceParams := []*restful.Parameter{namespaceParam} namespaceParams := []*restful.Parameter{namespaceParam}
itemPath := path + "/{name}" itemPath := resource + "/{name}"
if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource
}
nameParams := append(namespaceParams, nameParam) nameParams := append(namespaceParams, nameParam)
namer := legacyScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)} namer := legacyScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)}
actions = appendIf(actions, action{"LIST", path, namespaceParams, namer}, isLister) actions = appendIf(actions, action{"LIST", resource, namespaceParams, namer}, isLister)
actions = appendIf(actions, action{"POST", path, namespaceParams, namer}, isCreater) actions = appendIf(actions, action{"POST", resource, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)

View File

@ -372,7 +372,7 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter)
func (m *Master) init(c *Config) { func (m *Master) init(c *Config) {
boundPodFactory := &pod.BasicBoundPodFactory{} boundPodFactory := &pod.BasicBoundPodFactory{}
podStorage, bindingStorage := podetcd.NewREST(c.EtcdHelper, boundPodFactory) podStorage, bindingStorage, podStatusStorage := podetcd.NewREST(c.EtcdHelper, boundPodFactory)
podRegistry := pod.NewRegistry(podStorage) podRegistry := pod.NewRegistry(podStorage)
eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())) eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds()))
@ -407,6 +407,7 @@ func (m *Master) init(c *Config) {
// TODO: Factor out the core API registration // TODO: Factor out the core API registration
m.storage = map[string]apiserver.RESTStorage{ m.storage = map[string]apiserver.RESTStorage{
"pods": podStorage, "pods": podStorage,
"pods/status": podStatusStorage,
"bindings": bindingStorage, "bindings": bindingStorage,
"replicationControllers": controller.NewREST(registry, podRegistry), "replicationControllers": controller.NewREST(registry, podRegistry),

View File

@ -41,7 +41,7 @@ func NewTestEtcdRegistry(client tools.EtcdClient) *Registry {
func NewTestEtcdRegistryWithPods(client tools.EtcdClient) *Registry { func NewTestEtcdRegistryWithPods(client tools.EtcdClient) *Registry {
helper := tools.EtcdHelper{client, latest.Codec, tools.RuntimeVersionAdapter{latest.ResourceVersioner}} 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)) registry := NewRegistry(helper, pod.NewRegistry(podStorage))
return registry return registry
} }

View File

@ -39,9 +39,8 @@ type REST struct {
} }
// NewREST returns a RESTStorage object that will work against pods. // 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" prefix := "/registry/pods"
bindings := &podLifecycle{h}
store := &etcdgeneric.Etcd{ store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Pod{} }, NewFunc: func() runtime.Object { return &api.Pod{} },
NewListFunc: func() runtime.Object { return &api.PodList{} }, NewListFunc: func() runtime.Object { return &api.PodList{} },
@ -56,17 +55,20 @@ func NewREST(h tools.EtcdHelper, factory pod.BoundPodFactory) (*REST, *BindingRE
}, },
EndpointName: "pods", EndpointName: "pods",
CreateStrategy: pod.Strategy,
UpdateStrategy: pod.Strategy,
AfterUpdate: bindings.AfterUpdate,
ReturnDeletedObject: true,
AfterDelete: bindings.AfterDelete,
Helper: h, 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 // 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 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)
}

View File

@ -65,11 +65,11 @@ func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
return fakeEtcdClient, helper 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) fakeEtcdClient, h := newHelper(t)
storage, bindingStorage := NewREST(h, &pod.BasicBoundPodFactory{}) storage, bindingStorage, statusStorage := NewREST(h, &pod.BasicBoundPodFactory{})
storage = storage.WithPodStatus(&fakeCache{statusToReturn: &api.PodStatus{}}) storage = storage.WithPodStatus(&fakeCache{statusToReturn: &api.PodStatus{}})
return storage, bindingStorage, fakeEtcdClient, h return storage, bindingStorage, statusStorage, fakeEtcdClient, h
} }
func validNewPod() *api.Pod { func validNewPod() *api.Pod {
@ -104,13 +104,13 @@ func validChangedPod() *api.Pod {
} }
func TestStorage(t *testing.T) { func TestStorage(t *testing.T) {
storage, _, _, _ := newStorage(t) storage, _, _, _, _ := newStorage(t)
pod.NewRegistry(storage) pod.NewRegistry(storage)
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{statusToReturn: &api.PodStatus{}} cache := &fakeCache{statusToReturn: &api.PodStatus{}}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
test := resttest.New(t, storage, fakeEtcdClient.SetError) 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) { func TestCreateRegistryError(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.Err = fmt.Errorf("test error") fakeEtcdClient.Err = fmt.Errorf("test error")
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
pod := validNewPod() pod := validNewPod()
_, err := storage.Create(api.NewDefaultContext(), pod) _, err := storage.Create(api.NewDefaultContext(), pod)
@ -151,7 +151,7 @@ func TestCreateRegistryError(t *testing.T) {
func TestCreateSetsFields(t *testing.T) { func TestCreateSetsFields(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{statusToReturn: &api.PodStatus{}} cache := &fakeCache{statusToReturn: &api.PodStatus{}}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
pod := validNewPod() pod := validNewPod()
@ -175,7 +175,7 @@ func TestCreateSetsFields(t *testing.T) {
func TestListError(t *testing.T) { func TestListError(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.Err = fmt.Errorf("test error") fakeEtcdClient.Err = fmt.Errorf("test error")
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{} cache := &fakeCache{}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
pods, err := storage.List(api.NewDefaultContext(), labels.Everything(), labels.Everything()) 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} cache := &fakeCache{errorToReturn: client.ErrPodInfoNotAvailable}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -228,7 +228,7 @@ func TestListEmptyPodList(t *testing.T) {
E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound), E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound),
} }
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{} cache := &fakeCache{}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
pods, err := storage.List(api.NewContext(), labels.Everything(), labels.Everything()) 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}} cache := &fakeCache{statusToReturn: &api.PodStatus{Phase: api.PodRunning}}
storage = storage.WithPodStatus(cache) 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}} cache := &fakeCache{statusToReturn: &api.PodStatus{Phase: api.PodRunning}}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -384,7 +384,7 @@ func TestListPodListSelection(t *testing.T) {
} }
func TestPodDecode(t *testing.T) { func TestPodDecode(t *testing.T) {
storage, _ := NewREST(tools.EtcdHelper{}, nil) storage, _, _ := NewREST(tools.EtcdHelper{}, nil)
expected := validNewPod() expected := validNewPod()
body, err := latest.Codec.Encode(expected) body, err := latest.Codec.Encode(expected)
if err != nil { 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}} cache := &fakeCache{statusToReturn: &api.PodStatus{Phase: api.PodRunning}}
storage = storage.WithPodStatus(cache) 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} cache := &fakeCache{errorToReturn: client.ErrPodInfoNotAvailable}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -461,7 +461,7 @@ func TestGetCacheError(t *testing.T) {
func TestPodStorageValidatesCreate(t *testing.T) { func TestPodStorageValidatesCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.Err = fmt.Errorf("test error") fakeEtcdClient.Err = fmt.Errorf("test error")
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{statusToReturn: &api.PodStatus{}} cache := &fakeCache{statusToReturn: &api.PodStatus{}}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -481,7 +481,7 @@ func TestPodStorageValidatesCreate(t *testing.T) {
// TODO: remove, this is covered by RESTTest.TestCreate // TODO: remove, this is covered by RESTTest.TestCreate
func TestCreatePod(t *testing.T) { func TestCreatePod(t *testing.T) {
_, helper := newHelper(t) _, helper := newHelper(t)
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{statusToReturn: &api.PodStatus{}} cache := &fakeCache{statusToReturn: &api.PodStatus{}}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -505,7 +505,7 @@ func TestCreatePod(t *testing.T) {
// TODO: remove, this is covered by RESTTest.TestCreate // TODO: remove, this is covered by RESTTest.TestCreate
func TestCreateWithConflictingNamespace(t *testing.T) { func TestCreateWithConflictingNamespace(t *testing.T) {
_, helper := newHelper(t) _, helper := newHelper(t)
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{} cache := &fakeCache{}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -536,7 +536,7 @@ func TestUpdateWithConflictingNamespace(t *testing.T) {
}, },
}, },
} }
storage, _ := NewREST(helper, nil) storage, _, _ := NewREST(helper, nil)
cache := &fakeCache{} cache := &fakeCache{}
storage = storage.WithPodStatus(cache) 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}} cache := &fakeCache{statusToReturn: &api.PodStatus{PodIP: expectedIP}}
storage = storage.WithPodStatus(cache) 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{}} cache := &fakeCache{statusToReturn: &api.PodStatus{}}
storage = storage.WithPodStatus(cache) storage = storage.WithPodStatus(cache)
@ -730,7 +730,7 @@ func TestDeletePod(t *testing.T) {
// TestEtcdGetDifferentNamespace ensures same-name pods in different namespaces do not clash // TestEtcdGetDifferentNamespace ensures same-name pods in different namespaces do not clash
func TestEtcdGetDifferentNamespace(t *testing.T) { func TestEtcdGetDifferentNamespace(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx1 := api.NewDefaultContext() ctx1 := api.NewDefaultContext()
ctx2 := api.WithNamespace(api.NewContext(), "other") ctx2 := api.WithNamespace(api.NewContext(), "other")
@ -768,7 +768,7 @@ func TestEtcdGetDifferentNamespace(t *testing.T) {
} }
func TestEtcdGet(t *testing.T) { func TestEtcdGet(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0) 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) { func TestEtcdGetNotFound(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.Data[key] = tools.EtcdResponseWithError{
@ -799,7 +799,7 @@ func TestEtcdGetNotFound(t *testing.T) {
} }
func TestEtcdCreate(t *testing.T) { func TestEtcdCreate(t *testing.T) {
registry, bindingRegistry, fakeClient, _ := newStorage(t) registry, bindingRegistry, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
@ -847,7 +847,7 @@ func TestEtcdCreate(t *testing.T) {
} }
func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { func TestEtcdCreateFailsWithoutNamespace(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
fakeClient.TestIndex = true fakeClient.TestIndex = true
pod := validNewPod() pod := validNewPod()
pod.Namespace = "" pod.Namespace = ""
@ -859,7 +859,7 @@ func TestEtcdCreateFailsWithoutNamespace(t *testing.T) {
} }
func TestEtcdCreateAlreadyExisting(t *testing.T) { func TestEtcdCreateAlreadyExisting(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.Data[key] = tools.EtcdResponseWithError{
@ -877,7 +877,7 @@ func TestEtcdCreateAlreadyExisting(t *testing.T) {
} }
func TestEtcdCreateWithContainersError(t *testing.T) { func TestEtcdCreateWithContainersError(t *testing.T) {
registry, bindingRegistry, fakeClient, _ := newStorage(t) registry, bindingRegistry, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
@ -915,7 +915,7 @@ func TestEtcdCreateWithContainersError(t *testing.T) {
} }
func TestEtcdCreateWithContainersNotFound(t *testing.T) { func TestEtcdCreateWithContainersNotFound(t *testing.T) {
registry, bindingRegistry, fakeClient, _ := newStorage(t) registry, bindingRegistry, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
@ -968,7 +968,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) {
} }
func TestEtcdCreateWithExistingContainers(t *testing.T) { func TestEtcdCreateWithExistingContainers(t *testing.T) {
registry, bindingRegistry, fakeClient, _ := newStorage(t) registry, bindingRegistry, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
@ -1020,7 +1020,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
} }
func TestEtcdUpdateNotFound(t *testing.T) { func TestEtcdUpdateNotFound(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
@ -1046,7 +1046,7 @@ func TestEtcdUpdateNotFound(t *testing.T) {
} }
func TestEtcdUpdateNotScheduled(t *testing.T) { func TestEtcdUpdateNotScheduled(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
@ -1070,7 +1070,7 @@ func TestEtcdUpdateNotScheduled(t *testing.T) {
} }
func TestEtcdUpdateScheduled(t *testing.T) { func TestEtcdUpdateScheduled(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true 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) { func TestEtcdDeletePod(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
@ -1212,7 +1284,7 @@ func TestEtcdDeletePod(t *testing.T) {
} }
func TestEtcdDeletePodMultipleContainers(t *testing.T) { func TestEtcdDeletePodMultipleContainers(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := registry.store.KeyFunc(ctx, "foo") key, _ := registry.store.KeyFunc(ctx, "foo")
@ -1252,7 +1324,7 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) {
} }
func TestEtcdEmptyList(t *testing.T) { func TestEtcdEmptyList(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
key := registry.store.KeyRootFunc(ctx) key := registry.store.KeyRootFunc(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.Data[key] = tools.EtcdResponseWithError{
@ -1275,7 +1347,7 @@ func TestEtcdEmptyList(t *testing.T) {
} }
func TestEtcdListNotFound(t *testing.T) { func TestEtcdListNotFound(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
key := registry.store.KeyRootFunc(ctx) key := registry.store.KeyRootFunc(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.Data[key] = tools.EtcdResponseWithError{
@ -1293,7 +1365,7 @@ func TestEtcdListNotFound(t *testing.T) {
} }
func TestEtcdList(t *testing.T) { func TestEtcdList(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
key := registry.store.KeyRootFunc(ctx) key := registry.store.KeyRootFunc(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.Data[key] = tools.EtcdResponseWithError{
@ -1333,7 +1405,7 @@ func TestEtcdList(t *testing.T) {
} }
func TestEtcdWatchPods(t *testing.T) { func TestEtcdWatchPods(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
watching, err := registry.Watch(ctx, watching, err := registry.Watch(ctx,
labels.Everything(), labels.Everything(),
@ -1360,7 +1432,7 @@ func TestEtcdWatchPods(t *testing.T) {
} }
func TestEtcdWatchPodsMatch(t *testing.T) { func TestEtcdWatchPodsMatch(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
watching, err := registry.Watch(ctx, watching, err := registry.Watch(ctx,
labels.SelectorFromSet(labels.Set{"name": "foo"}), labels.SelectorFromSet(labels.Set{"name": "foo"}),
@ -1399,7 +1471,7 @@ func TestEtcdWatchPodsMatch(t *testing.T) {
} }
func TestEtcdWatchPodsNotMatch(t *testing.T) { func TestEtcdWatchPodsNotMatch(t *testing.T) {
registry, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
watching, err := registry.Watch(ctx, watching, err := registry.Watch(ctx,
labels.SelectorFromSet(labels.Set{"name": "foo"}), labels.SelectorFromSet(labels.Set{"name": "foo"}),

View File

@ -70,6 +70,17 @@ func (podStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErro
return validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod)) 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. // PodStatusGetter is an interface used by Pods to fetch and retrieve status info.
type PodStatusGetter interface { type PodStatusGetter interface {
GetPodStatus(namespace, name string) (*api.PodStatus, error) GetPodStatus(namespace, name string) (*api.PodStatus, error)