Merge pull request #3404 from smarterclayton/method_not_allowed

Allow RESTStorage objects to not implement methods
This commit is contained in:
Daniel Smith 2015-01-12 13:21:10 -08:00
commit 6f43074143
23 changed files with 278 additions and 177 deletions

View File

@ -159,6 +159,19 @@ func NewBadRequest(reason string) error {
}}
}
// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
func NewMethodNotSupported(kind, action string) error {
return &StatusError{api.Status{
Status: api.StatusFailure,
Code: http.StatusMethodNotAllowed,
Reason: api.StatusReasonMethodNotAllowed,
Details: &api.StatusDetails{
Kind: kind,
},
Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, kind),
}}
}
// NewInternalError returns an error indicating the item is invalid and cannot be processed.
func NewInternalError(err error) error {
return &StatusError{api.Status{
@ -192,6 +205,12 @@ func IsInvalid(err error) bool {
return reasonForError(err) == api.StatusReasonInvalid
}
// IsMethodNotSupported determines if the err is an error which indicates the provided action could not
// be performed because it is not supported by the server.
func IsMethodNotSupported(err error) bool {
return reasonForError(err) == api.StatusReasonMethodNotAllowed
}
// IsBadRequest determines if err is an error which indicates that the request is invalid.
func IsBadRequest(err error) bool {
return reasonForError(err) == api.StatusReasonBadRequest

View File

@ -40,6 +40,12 @@ func TestErrorNew(t *testing.T) {
if IsInvalid(err) {
t.Errorf("expected to not be %s", api.StatusReasonInvalid)
}
if IsBadRequest(err) {
t.Errorf("expected to not be %s", api.StatusReasonBadRequest)
}
if IsMethodNotSupported(err) {
t.Errorf("expected to not be %s", api.StatusReasonMethodNotAllowed)
}
if !IsConflict(NewConflict("test", "2", errors.New("message"))) {
t.Errorf("expected to be conflict")
@ -50,6 +56,12 @@ func TestErrorNew(t *testing.T) {
if !IsInvalid(NewInvalid("test", "2", nil)) {
t.Errorf("expected to be %s", api.StatusReasonInvalid)
}
if !IsBadRequest(NewBadRequest("reason")) {
t.Errorf("expected to be %s", api.StatusReasonBadRequest)
}
if !IsMethodNotSupported(NewMethodNotSupported("foo", "delete")) {
t.Errorf("expected to be %s", api.StatusReasonMethodNotAllowed)
}
}
func TestNewInvalid(t *testing.T) {

View File

@ -931,6 +931,11 @@ const (
// data was invalid. API calls that return BadRequest can never succeed.
StatusReasonBadRequest StatusReason = "BadRequest"
// StatusReasonMethodNotAllowed means that the action the client attempted to perform on the
// resource was not supported by the code - for instance, attempting to delete a resource that
// can only be created. API calls that return MethodNotAllowed can never succeed.
StatusReasonMethodNotAllowed StatusReason = "MethodNotAllowed"
// StatusReasonInternalError indicates that an internal error occurred, it is unexpected
// and the outcome of the call is unknown.
// Details (optional):

View File

@ -129,62 +129,70 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string")
ws.Route(
addParamIf(
ws.POST(path).To(h).
Doc("create a "+kind).
Operation("create"+kind).
Reads(versionedObject), // from the request
namespaceParam, namespaceScope))
// TODO: This seems like a hack. Add NewList() to storage?
listKind := kind + "List"
if _, ok := kinds[listKind]; !ok {
glog.V(1).Infof("no list type: %v\n", listKind)
createRoute := ws.POST(path).To(h).
Doc("create a " + kind).
Operation("create" + kind).
Reads(versionedObject) // from the request
addParamIf(createRoute, namespaceParam, namespaceScope)
if _, ok := storage.(RESTCreater); ok {
ws.Route(createRoute.Reads(versionedObject)) // from the request
} else {
versionedListPtr, err := api.Scheme.New(version, listKind)
if err != nil {
glog.Errorf("error making list: %v\n", err)
} else {
versionedList := indirectArbitraryPointer(versionedListPtr)
glog.V(3).Infoln("type: ", reflect.TypeOf(versionedList))
ws.Route(
addParamIf(
ws.GET(path).To(h).
Doc("list objects of kind "+kind).
Operation("list"+kind).
Returns(http.StatusOK, "OK", versionedList),
namespaceParam, namespaceScope))
}
ws.Route(createRoute.Returns(http.StatusMethodNotAllowed, "creating objects is not supported", nil))
}
ws.Route(
addParamIf(
ws.GET(path+"/{name}").To(h).
Doc("read the specified "+kind).
Operation("read"+kind).
Param(nameParam).
Writes(versionedObject), // on the response
namespaceParam, namespaceScope))
listRoute := ws.GET(path).To(h).
Doc("list objects of kind " + kind).
Operation("list" + kind)
addParamIf(listRoute, namespaceParam, namespaceScope)
if lister, ok := storage.(RESTLister); ok {
list := lister.NewList()
_, listKind, err := api.Scheme.ObjectVersionAndKind(list)
versionedListPtr, err := api.Scheme.New(version, listKind)
if err != nil {
glog.Errorf("error making list object: %v\n", err)
return
}
versionedList := indirectArbitraryPointer(versionedListPtr)
glog.V(3).Infoln("type: ", reflect.TypeOf(versionedList))
ws.Route(listRoute.Returns(http.StatusOK, "OK", versionedList))
} else {
ws.Route(listRoute.Returns(http.StatusMethodNotAllowed, "listing objects is not supported", nil))
}
ws.Route(
addParamIf(
ws.PUT(path+"/{name}").To(h).
Doc("update the specified "+kind).
Operation("update"+kind).
Param(nameParam).
Reads(versionedObject), // from the request
namespaceParam, namespaceScope))
getRoute := ws.GET(path + "/{name}").To(h).
Doc("read the specified " + kind).
Operation("read" + kind).
Param(nameParam)
addParamIf(getRoute, namespaceParam, namespaceScope)
if _, ok := storage.(RESTGetter); ok {
ws.Route(getRoute.Writes(versionedObject)) // on the response
} else {
ws.Route(ws.GET(path+"/{name}").To(h).
Returns(http.StatusMethodNotAllowed, "reading individual objects is not supported", nil))
}
updateRoute := ws.PUT(path + "/{name}").To(h).
Doc("update the specified " + kind).
Operation("update" + kind).
Param(nameParam)
addParamIf(updateRoute, namespaceParam, namespaceScope)
if _, ok := storage.(RESTUpdater); ok {
ws.Route(updateRoute.Reads(versionedObject)) // from the request
} else {
ws.Route(updateRoute.Returns(http.StatusMethodNotAllowed, "updating objects is not supported", nil))
}
// TODO: Support PATCH
ws.Route(
addParamIf(
ws.DELETE(path+"/{name}").To(h).
Doc("delete the specified "+kind).
Operation("delete"+kind).
Param(nameParam),
namespaceParam, namespaceScope))
deleteRoute := ws.DELETE(path + "/{name}").To(h).
Doc("delete the specified " + kind).
Operation("delete" + kind).
Param(nameParam)
addParamIf(deleteRoute, namespaceParam, namespaceScope)
if _, ok := storage.(RESTDeleter); ok {
ws.Route(deleteRoute)
} else {
ws.Route(deleteRoute.Returns(http.StatusMethodNotAllowed, "deleting objects is not supported", nil))
}
}
// Adds the given param to the given route builder if shouldAdd is true. Does nothing if shouldAdd is false.

View File

@ -183,6 +183,10 @@ func (storage *SimpleRESTStorage) New() runtime.Object {
return &Simple{}
}
func (storage *SimpleRESTStorage) NewList() runtime.Object {
return &SimpleList{}
}
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
storage.created = obj.(*Simple)
if err := storage.errors["create"]; err != nil {
@ -288,6 +292,68 @@ func TestNotFound(t *testing.T) {
}
}
type UnimplementedRESTStorage struct{}
func (UnimplementedRESTStorage) New() runtime.Object {
return &Simple{}
}
func TestMethodNotAllowed(t *testing.T) {
type T struct {
Method string
Path string
}
cases := map[string]T{
"GET object": {"GET", "/prefix/version/foo/bar"},
"GET list": {"GET", "/prefix/version/foo"},
"POST list": {"POST", "/prefix/version/foo"},
"PUT object": {"PUT", "/prefix/version/foo/bar"},
"DELETE object": {"DELETE", "/prefix/version/foo/bar"},
//"watch list": {"GET", "/prefix/version/watch/foo"},
//"watch object": {"GET", "/prefix/version/watch/foo/bar"},
"proxy object": {"GET", "/prefix/version/proxy/foo/bar"},
"redirect object": {"GET", "/prefix/version/redirect/foo/bar"},
}
handler := Handle(map[string]RESTStorage{
"foo": UnimplementedRESTStorage{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
for k, v := range cases {
request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`)))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
response, err := client.Do(request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
continue
}
defer response.Body.Close()
data, _ := ioutil.ReadAll(response.Body)
t.Logf("resp: %s", string(data))
if response.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("%s: expected %d for %s, Got %s", k, http.StatusMethodNotAllowed, v.Method, string(data))
continue
}
obj, err := codec.Decode(data)
if err != nil {
t.Errorf("%s: unexpected decode error: %v", k, err)
continue
}
status, ok := obj.(*api.Status)
if !ok {
t.Errorf("%s: unexpected object: %#v", k, obj)
continue
}
if status.Reason != api.StatusReasonMethodNotAllowed {
t.Errorf("%s: unexpected status: %#v", k, status)
}
}
}
func TestVersion(t *testing.T) {
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)

View File

@ -24,28 +24,43 @@ import (
)
// RESTStorage is a generic interface for RESTful storage services.
// Resources which are exported to the RESTful API of apiserver need to implement this interface.
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
// that objects may implement any of the REST* interfaces.
// TODO: implement dynamic introspection (so GenericREST objects can indicate what they implement)
type RESTStorage interface {
// New returns an empty object that can be used with Create and Update after request data has been put into it.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object
}
type RESTLister interface {
// NewList returns an empty object that can be used with the List call.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
NewList() runtime.Object
// List selects resources in the storage which match to the selector.
List(ctx api.Context, label, field labels.Selector) (runtime.Object, error)
}
type RESTGetter interface {
// Get finds a resource in the storage by id and returns it.
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
// returned error value err when the specified resource is not found.
Get(ctx api.Context, id string) (runtime.Object, error)
}
type RESTDeleter interface {
// Delete finds a resource in the storage and deletes it.
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
// returned error value err when the specified resource is not found.
Delete(ctx api.Context, id string) (<-chan RESTResult, error)
}
type RESTCreater interface {
// Create creates a new version of a resource.
Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
}
type RESTUpdater interface {
// Update finds a resource in the storage and updates it. Some implementations
// may allow updates creates the object - they should set the Created flag of
// the returned RESTResultto true. In the event of an asynchronous error returned

View File

@ -30,6 +30,7 @@ import (
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -106,7 +107,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
redirector, ok := storage.(Redirector)
if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
notFound(w, req)
errorJSON(errors.NewMethodNotSupported(kind, "proxy"), r.codec, w)
return
}

View File

@ -20,6 +20,7 @@ import (
"net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
@ -53,7 +54,7 @@ func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
redirector, ok := storage.(Redirector)
if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
notFound(w, req)
errorJSON(errors.NewMethodNotSupported(kind, "redirect"), r.codec, w)
return
}

View File

@ -23,7 +23,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@ -48,14 +48,13 @@ func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
notFound(w, req)
return
}
storage := h.storage[kind]
if storage == nil {
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
storage, ok := h.storage[kind]
if !ok {
notFound(w, req)
return
}
h.handleRESTStorage(parts, req, w, storage, namespace)
h.handleRESTStorage(parts, req, w, storage, namespace, kind)
}
// Sets the SelfLink field of the object.
@ -148,7 +147,7 @@ func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(
// sync=[false|true] Synchronous request (only applies to create, update, delete operations)
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
// labels=<label-selector> Used for filtering list operations
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage, namespace string) {
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage, namespace, kind string) {
ctx := api.WithNamespace(api.NewContext(), namespace)
sync := req.URL.Query().Get("sync") == "true"
timeout := parseTimeout(req.URL.Query().Get("timeout"))
@ -166,7 +165,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
errorJSON(err, h.codec, w)
return
}
list, err := storage.List(ctx, label, field)
lister, ok := storage.(RESTLister)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "list"), h.codec, w)
return
}
list, err := lister.List(ctx, label, field)
if err != nil {
errorJSON(err, h.codec, w)
return
@ -177,7 +181,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
}
writeJSON(http.StatusOK, h.codec, list, w)
case 2:
item, err := storage.Get(ctx, parts[1])
getter, ok := storage.(RESTGetter)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "get"), h.codec, w)
return
}
item, err := getter.Get(ctx, parts[1])
if err != nil {
errorJSON(err, h.codec, w)
return
@ -196,6 +205,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
notFound(w, req)
return
}
creater, ok := storage.(RESTCreater)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "create"), h.codec, w)
return
}
body, err := readBody(req)
if err != nil {
errorJSON(err, h.codec, w)
@ -215,7 +230,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
return
}
out, err := storage.Create(ctx, obj)
out, err := creater.Create(ctx, obj)
if err != nil {
errorJSON(err, h.codec, w)
return
@ -228,6 +243,11 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
notFound(w, req)
return
}
deleter, ok := storage.(RESTDeleter)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "delete"), h.codec, w)
return
}
// invoke admission control
err := h.admissionControl.Admit(admission.NewAttributesRecord(nil, namespace, parts[0], "DELETE"))
@ -236,7 +256,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
return
}
out, err := storage.Delete(ctx, parts[1])
out, err := deleter.Delete(ctx, parts[1])
if err != nil {
errorJSON(err, h.codec, w)
return
@ -249,6 +269,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
notFound(w, req)
return
}
updater, ok := storage.(RESTUpdater)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "create"), h.codec, w)
return
}
body, err := readBody(req)
if err != nil {
errorJSON(err, h.codec, w)
@ -268,7 +294,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
return
}
out, err := storage.Update(ctx, obj)
out, err := updater.Update(ctx, obj)
if err != nil {
errorJSON(err, h.codec, w)
return

View File

@ -24,6 +24,7 @@ import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@ -98,34 +99,35 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
notFound(w, req)
return
}
if watcher, ok := storage.(ResourceWatcher); ok {
label, field, resourceVersion, err := getWatchParams(req.URL.Query())
if err != nil {
errorJSON(err, h.codec, w)
return
}
watching, err := watcher.Watch(ctx, label, field, resourceVersion)
if err != nil {
errorJSON(err, h.codec, w)
return
}
// TODO: This is one watch per connection. We want to multiplex, so that
// multiple watches of the same thing don't create two watches downstream.
watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) {
if err := h.setSelfLinkAddName(obj, req); err != nil {
glog.Errorf("Failed to set self link for object %#v", obj)
}
}}
if isWebsocketRequest(req) {
websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
} else {
watchServer.ServeHTTP(w, req)
}
watcher, ok := storage.(ResourceWatcher)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "watch"), h.codec, w)
return
}
notFound(w, req)
label, field, resourceVersion, err := getWatchParams(req.URL.Query())
if err != nil {
errorJSON(err, h.codec, w)
return
}
watching, err := watcher.Watch(ctx, label, field, resourceVersion)
if err != nil {
errorJSON(err, h.codec, w)
return
}
// TODO: This is one watch per connection. We want to multiplex, so that
// multiple watches of the same thing don't create two watches downstream.
watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) {
if err := h.setSelfLinkAddName(obj, req); err != nil {
glog.Errorf("Failed to set self link for object %#v", obj)
}
}}
if isWebsocketRequest(req) {
websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
} else {
watchServer.ServeHTTP(w, req)
}
}
// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.

View File

@ -21,6 +21,7 @@ import (
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/golang/glog"
)
@ -94,7 +95,7 @@ func (m *Master) createMasterServiceIfNeeded(serviceName string, port int) error
}
// Kids, don't do this at home: this is a hack. There's no good way to call the business
// logic which lives in the REST object from here.
c, err := m.storage["services"].Create(ctx, svc)
c, err := m.storage["services"].(apiserver.RESTCreater).Create(ctx, svc)
if err != nil {
return err
}

View File

@ -33,6 +33,9 @@ import (
// TODO: considering that the only difference between the various client types
// and RESTStorage type is the type of the arguments, maybe use "go generate" to
// write a specialized adaptor for every client type?
//
// TODO: this also means that pod and node API endpoints have to be colocated in the same
// process
func RESTStorageToNodes(storage apiserver.RESTStorage) client.NodesInterface {
return &nodeAdaptor{storage}
}
@ -61,7 +64,7 @@ func (n *nodeAdaptor) Create(minion *api.Node) (*api.Node, error) {
// List lists all the nodes in the cluster.
func (n *nodeAdaptor) List() (*api.NodeList, error) {
ctx := api.NewContext()
obj, err := n.storage.List(ctx, labels.Everything(), labels.Everything())
obj, err := n.storage.(apiserver.RESTLister).List(ctx, labels.Everything(), labels.Everything())
if err != nil {
return nil, err
}
@ -71,7 +74,7 @@ func (n *nodeAdaptor) List() (*api.NodeList, error) {
// Get gets an existing minion
func (n *nodeAdaptor) Get(name string) (*api.Node, error) {
ctx := api.NewContext()
obj, err := n.storage.Get(ctx, name)
obj, err := n.storage.(apiserver.RESTGetter).Get(ctx, name)
if err != nil {
return nil, err
}

View File

@ -20,9 +20,7 @@ import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
@ -40,21 +38,6 @@ func NewREST(bindingRegistry Registry) *REST {
}
}
// List returns an error because bindings are write-only objects.
func (*REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
return nil, errors.NewNotFound("binding", "list")
}
// Get returns an error because bindings are write-only objects.
func (*REST) Get(ctx api.Context, id string) (runtime.Object, error) {
return nil, errors.NewNotFound("binding", id)
}
// Delete returns an error because bindings are write-only objects.
func (*REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
return nil, errors.NewNotFound("binding", id)
}
// New returns a new binding object fit for having data unmarshalled into it.
func (*REST) New() runtime.Object {
return &api.Binding{}
@ -73,8 +56,3 @@ func (b *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RES
return &api.Status{Status: api.StatusSuccess}, nil
}), nil
}
// Update returns an error-- this object may not be updated.
func (b *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
return nil, fmt.Errorf("bindings may not be changed.")
}

View File

@ -24,7 +24,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)
func TestNewREST(t *testing.T) {
@ -51,30 +50,6 @@ func TestNewREST(t *testing.T) {
}
}
func TestRESTUnsupported(t *testing.T) {
var ctx api.Context
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error { return nil },
}
b := NewREST(mockRegistry)
if _, err := b.Delete(ctx, "binding id"); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := b.Update(ctx, &api.Binding{PodID: "foo", Host: "new machine"}); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := b.Get(ctx, "binding id"); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := b.List(ctx, labels.Set{"name": "foo"}.AsSelector(), labels.Everything()); err == nil {
t.Errorf("unexpected non-error")
}
// Try sending wrong object just to get 100% coverage
if _, err := b.Create(ctx, &api.Pod{}); err == nil {
t.Errorf("unexpected non-error")
}
}
func TestRESTPost(t *testing.T) {
table := []struct {
b *api.Binding

View File

@ -122,6 +122,10 @@ func (*REST) New() runtime.Object {
return &api.ReplicationController{}
}
func (*REST) NewList() runtime.Object {
return &api.ReplicationControllerList{}
}
// Update replaces a given ReplicationController instance with an existing
// instance in storage.registry.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {

View File

@ -96,12 +96,11 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
}), nil
}
// Delete satisfies the RESTStorage interface but is unimplemented.
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
return nil, errors.NewBadRequest("Endpoints are read-only")
}
// New implements the RESTStorage interface.
func (rs REST) New() runtime.Object {
return &api.Endpoints{}
}
func (*REST) NewList() runtime.Object {
return &api.EndpointsList{}
}

View File

@ -96,14 +96,3 @@ func TestEndpointsRegistryList(t *testing.T) {
t.Errorf("Unexpected resource version: %#v", sl)
}
}
func TestEndpointsRegistryDelete(t *testing.T) {
registry := registrytest.NewServiceRegistry()
storage := NewREST(registry)
_, err := storage.Delete(api.NewContext(), "n/a")
if err == nil {
t.Error("unexpected non-error")
} else if !errors.IsBadRequest(err) {
t.Errorf("unexpected error: %v", err)
}
}

View File

@ -128,7 +128,6 @@ func (*REST) New() runtime.Object {
return &api.Event{}
}
// Update returns an error: Events are not mutable.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
return nil, fmt.Errorf("not allowed: 'Event' objects are not mutable")
func (*REST) NewList() runtime.Object {
return &api.EventList{}
}

View File

@ -172,20 +172,6 @@ func TestRESTgetAttrs(t *testing.T) {
}
}
func TestRESTUpdate(t *testing.T) {
_, rest := NewTestREST()
eventA := testEvent("foo")
c, err := rest.Create(api.NewDefaultContext(), eventA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
<-c
_, err = rest.Update(api.NewDefaultContext(), eventA)
if err == nil {
t.Errorf("unexpected non-error")
}
}
func TestRESTList(t *testing.T) {
reg, rest := NewTestREST()
eventA := &api.Event{

View File

@ -104,6 +104,10 @@ func (rs *REST) New() runtime.Object {
return &api.Node{}
}
func (*REST) NewList() runtime.Object {
return &api.NodeList{}
}
// Update satisfies the RESTStorage interface.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
minion, ok := obj.(*api.Node)

View File

@ -161,6 +161,10 @@ func (*REST) New() runtime.Object {
return &api.Pod{}
}
func (*REST) NewList() runtime.Object {
return &api.PodList{}
}
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
pod := obj.(*api.Pod)
if !api.ValidNamespace(ctx, &pod.ObjectMeta) {

View File

@ -217,6 +217,10 @@ func (*REST) New() runtime.Object {
return &api.Service{}
}
func (*REST) NewList() runtime.Object {
return &api.Service{}
}
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
service := obj.(*api.Service)
if !api.ValidNamespace(ctx, &service.ObjectMeta) {

View File

@ -222,7 +222,7 @@ func getTestRequests() []struct {
{"PUT", "/api/v1beta1/endpoints/a" + syncFlags, aEndpoints, code200},
{"GET", "/api/v1beta1/endpoints", "", code200},
{"GET", "/api/v1beta1/endpoints/a", "", code200},
{"DELETE", "/api/v1beta1/endpoints/a" + syncFlags, "", code400},
{"DELETE", "/api/v1beta1/endpoints/a" + syncFlags, "", code405},
// Normal methods on minions
{"GET", "/api/v1beta1/minions", "", code200},
@ -235,7 +235,7 @@ func getTestRequests() []struct {
// Normal methods on events
{"GET", "/api/v1beta1/events", "", code200},
{"POST", "/api/v1beta1/events" + syncFlags, aEvent, code200},
{"PUT", "/api/v1beta1/events/a" + syncFlags, aEvent, code500}, // See #2114 about why 500
{"PUT", "/api/v1beta1/events/a" + syncFlags, aEvent, code405},
{"GET", "/api/v1beta1/events", "", code200},
{"GET", "/api/v1beta1/events", "", code200},
{"GET", "/api/v1beta1/events/a", "", code200},
@ -245,10 +245,10 @@ func getTestRequests() []struct {
{"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only
{"POST", "/api/v1beta1/pods" + syncFlags, aPod, code200}, // Need a pod to bind or you get a 404
{"POST", "/api/v1beta1/bindings" + syncFlags, aBinding, code200},
{"PUT", "/api/v1beta1/bindings/a" + syncFlags, aBinding, code500}, // See #2114 about why 500
{"PUT", "/api/v1beta1/bindings/a" + syncFlags, aBinding, code405},
{"GET", "/api/v1beta1/bindings", "", code405},
{"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances
{"DELETE", "/api/v1beta1/bindings/a" + syncFlags, "", code404},
{"GET", "/api/v1beta1/bindings/a", "", code405}, // No bindings instances
{"DELETE", "/api/v1beta1/bindings/a" + syncFlags, "", code405},
// Non-existent object type.
{"GET", "/api/v1beta1/foo", "", code404},