mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Merge pull request #3404 from smarterclayton/method_not_allowed
Allow RESTStorage objects to not implement methods
This commit is contained in:
commit
6f43074143
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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{}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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{}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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},
|
||||
|
Loading…
Reference in New Issue
Block a user