From 03ecffa65b164ff2234b4521fa37d3d8c5cc5fdb Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Sun, 14 Dec 2014 20:13:17 -0500 Subject: [PATCH 1/2] round 1 - resize verb --- pkg/apiserver/apiserver.go | 8 +++ pkg/apiserver/handlers.go | 1 + pkg/apiserver/interfaces.go | 4 ++ pkg/apiserver/resize.go | 102 ++++++++++++++++++++++++++++++++ pkg/registry/controller/rest.go | 9 +++ 5 files changed, 124 insertions(+) create mode 100644 pkg/apiserver/resize.go diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 934aca0698d09..f1c8ec260abf0 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -178,6 +178,13 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, redirectHandler := &RedirectHandler{g.handler.storage, g.handler.codec} opHandler := &OperationHandler{g.handler.ops, g.handler.codec} + glog.Infof("-------------------------------------- Registering handler with prefix %s", prefix + "/resize/") + resizeHandler := &ResizeHandler{ + canonicalPrefix: prefix + "/resize/", + codec: g.handler.codec, + storage: g.handler.storage, + } + // Create a new WebService for this APIGroupVersion at the specified path prefix // TODO: Pass in more descriptive documentation ws := new(restful.WebService) @@ -233,6 +240,7 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, mux.Handle(prefix+"/redirect/", http.StripPrefix(prefix+"/redirect/", redirectHandler)) mux.Handle(prefix+"/operations", http.StripPrefix(prefix+"/operations", opHandler)) mux.Handle(prefix+"/operations/", http.StripPrefix(prefix+"/operations/", opHandler)) + mux.Handle(prefix+"/resize/", http.StripPrefix(prefix+"/resize/", resizeHandler)) container.Add(ws) } diff --git a/pkg/apiserver/handlers.go b/pkg/apiserver/handlers.go index a853050583ca1..6c60fd0e11eca 100644 --- a/pkg/apiserver/handlers.go +++ b/pkg/apiserver/handlers.go @@ -38,6 +38,7 @@ var specialVerbs = map[string]bool{ "proxy": true, "redirect": true, "watch": true, + "resize": true, } // KindFromRequest returns Kind if Kind can be extracted from the request. Otherwise, the empty string. diff --git a/pkg/apiserver/interfaces.go b/pkg/apiserver/interfaces.go index 72d5ebb102117..e1666fa4aa19f 100644 --- a/pkg/apiserver/interfaces.go +++ b/pkg/apiserver/interfaces.go @@ -53,6 +53,10 @@ type RESTStorage interface { Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) } +type Resizable interface { + Resize(ctx api.Context, id string, size int) (<-chan RESTResult, error) +} + // RESTResult indicates the result of a REST transformation. type RESTResult struct { // The result of this operation. May be nil if the operation has no meaningful diff --git a/pkg/apiserver/resize.go b/pkg/apiserver/resize.go new file mode 100644 index 0000000000000..893778f4edf16 --- /dev/null +++ b/pkg/apiserver/resize.go @@ -0,0 +1,102 @@ +package apiserver + +import ( + "strings" + "strconv" + + "net/http" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/httplog" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" +) + + +type ResizeHandler struct { + canonicalPrefix string + codec runtime.Codec + storage map[string]RESTStorage +} + +var supportedResizables = map[string] bool { + "replicationControllers": true, +} + +func (h *ResizeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + namespace := req.URL.Query().Get("namespace") + ctx := api.WithNamespaceDefaultIfNone(api.WithNamespace(api.NewDefaultContext(), namespace)) + replicaCount := req.URL.Query().Get("replicas") + inc := req.URL.Query().Get("inc") + + pathParts := strings.SplitN(req.URL.Path, "/", 3) + + if len(pathParts) < 2 { + notFound(w, req) + return + } + + resizableType := pathParts[0] + resizableName := pathParts[1] + + if !h.isSupportedType(resizableType) { + httplog.LogOf(req, w).Addf("%s is not a supported resizable type", resizableType) + notFound(w, req) + return + } + + storage := h.storage[resizableType] + if storage == nil { + httplog.LogOf(req, w).Addf("'%v' has no storage object", resizableType) + notFound(w, req) + return + } + + resizableStorage, ok := storage.(Resizable) + + if !ok { + w.Write([]byte("failed storage")) + //todo handle the error + } + + if len(replicaCount) > 0 { + //todo handle the error +// i, _ := strconv.ParseInt(replicaCount, 10, 32) +//// h.handleSetReplicaCount(i) +// +// + } else { + //todo handle the error + i, _ := strconv.ParseInt(inc, 10, 32) +// h.handleIncrement(i) + +// out, err := resizableStorage.Resize(ctx, resizableName, int(i)) + _, err := resizableStorage.Resize(ctx, resizableName, int(i)) + + if err != nil { + errorJSON(err, h.codec, w) + return + } + + obj, err := storage.Get(ctx, resizableName) + + if err != nil { + writeJSON(http.StatusInternalServerError, h.codec, obj, w) + return + } + + //todo this isn't updated right away so it doesn't immediately show the desired + //state, should I just return nothing or ok or something? + writeJSON(http.StatusOK, h.codec, obj, w) + } +} + +func (h *ResizeHandler) validateParameters(replicaCount string, inc string) bool { + //are they integers + //is only one set - warn, replica count will override increment behavior + return true +} + +func (h *ResizeHandler) isSupportedType(s string) bool { + _, ok := supportedResizables[s] + return ok +} diff --git a/pkg/registry/controller/rest.go b/pkg/registry/controller/rest.go index 4aba18034025e..d85c95dc4ca5f 100644 --- a/pkg/registry/controller/rest.go +++ b/pkg/registry/controller/rest.go @@ -96,6 +96,15 @@ func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) { return controller, err } +func (rs *REST) Resize(ctx api.Context, id string, size int) (<-chan apiserver.RESTResult, error) { + controller, err := rs.registry.GetController(ctx, id) + if err != nil { + return nil, err + } + controller.Spec.Replicas = controller.Spec.Replicas + size + return rs.Update(ctx, controller) +} + // List obtains a list of ReplicationControllers that match selector. func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { if !field.Empty() { From 44b834295b8cc0ec146eb95fea365d8280404ca3 Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Wed, 17 Dec 2014 14:37:28 -0500 Subject: [PATCH 2/2] expose some util methods in the resthandler for use in the package. Clean up error handling --- pkg/apiserver/apiserver.go | 3 +- pkg/apiserver/resize.go | 102 +++++++++++++++++------------------ pkg/apiserver/resthandler.go | 26 ++++----- 3 files changed, 64 insertions(+), 67 deletions(-) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index f1c8ec260abf0..3744de3045d2c 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -178,11 +178,12 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, redirectHandler := &RedirectHandler{g.handler.storage, g.handler.codec} opHandler := &OperationHandler{g.handler.ops, g.handler.codec} - glog.Infof("-------------------------------------- Registering handler with prefix %s", prefix + "/resize/") resizeHandler := &ResizeHandler{ canonicalPrefix: prefix + "/resize/", codec: g.handler.codec, storage: g.handler.storage, + ops: g.handler.ops, + timeout: g.handler.asyncOpWait, } // Create a new WebService for this APIGroupVersion at the specified path prefix diff --git a/pkg/apiserver/resize.go b/pkg/apiserver/resize.go index 893778f4edf16..17bc9a6ed95a3 100644 --- a/pkg/apiserver/resize.go +++ b/pkg/apiserver/resize.go @@ -1,34 +1,37 @@ package apiserver import ( - "strings" - "strconv" - + "fmt" "net/http" + "strconv" + "strings" + "time" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" - "github.com/GoogleCloudPlatform/kubernetes/pkg/httplog" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/httplog" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) - type ResizeHandler struct { - canonicalPrefix string - codec runtime.Codec - storage map[string]RESTStorage + canonicalPrefix string + codec runtime.Codec + storage map[string]RESTStorage + ops *Operations + timeout time.Duration } -var supportedResizables = map[string] bool { +var supportedResizables = map[string]bool{ "replicationControllers": true, } -func (h *ResizeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - namespace := req.URL.Query().Get("namespace") - ctx := api.WithNamespaceDefaultIfNone(api.WithNamespace(api.NewDefaultContext(), namespace)) - replicaCount := req.URL.Query().Get("replicas") - inc := req.URL.Query().Get("inc") +const ( + paramInc string = "inc" + paramNamespace string = "namespace" + urlDelimiter string = "/" +) - pathParts := strings.SplitN(req.URL.Path, "/", 3) +func (h *ResizeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + pathParts := strings.SplitN(req.URL.Path, urlDelimiter, 3) if len(pathParts) < 2 { notFound(w, req) @@ -37,10 +40,11 @@ func (h *ResizeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { resizableType := pathParts[0] resizableName := pathParts[1] + ctx := h.contextFromRequest(req) + inc := req.URL.Query().Get(paramInc) if !h.isSupportedType(resizableType) { - httplog.LogOf(req, w).Addf("%s is not a supported resizable type", resizableType) - notFound(w, req) + badRequest("The requested resource does not support resizing", w) return } @@ -53,50 +57,42 @@ func (h *ResizeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { resizableStorage, ok := storage.(Resizable) + //shouldn't ever get here if the supported types are correct, but check anyway if !ok { - w.Write([]byte("failed storage")) - //todo handle the error + badRequest("The requested resource does not support resizing", w) + return } - if len(replicaCount) > 0 { - //todo handle the error -// i, _ := strconv.ParseInt(replicaCount, 10, 32) -//// h.handleSetReplicaCount(i) -// -// - } else { - //todo handle the error - i, _ := strconv.ParseInt(inc, 10, 32) -// h.handleIncrement(i) - -// out, err := resizableStorage.Resize(ctx, resizableName, int(i)) - _, err := resizableStorage.Resize(ctx, resizableName, int(i)) - - if err != nil { - errorJSON(err, h.codec, w) - return - } - - obj, err := storage.Get(ctx, resizableName) - - if err != nil { - writeJSON(http.StatusInternalServerError, h.codec, obj, w) - return - } - - //todo this isn't updated right away so it doesn't immediately show the desired - //state, should I just return nothing or ok or something? - writeJSON(http.StatusOK, h.codec, obj, w) + i, err := strconv.ParseInt(inc, 10, 32) + + if err != nil { + badRequest(fmt.Sprintf("Unable to convert %v to an integer", inc), w) + return } + + out, err := resizableStorage.Resize(ctx, resizableName, int(i)) + + if err != nil { + errorJSON(err, h.codec, w) + return + } + + op := createOperation(h.ops, out, true, h.timeout, nil, 0) + finishReq(op, req, w, h.codec) } -func (h *ResizeHandler) validateParameters(replicaCount string, inc string) bool { - //are they integers - //is only one set - warn, replica count will override increment behavior - return true +func (h *ResizeHandler) contextFromRequest(req *http.Request) api.Context { + namespace := req.URL.Query().Get(paramNamespace) + ctx := api.WithNamespaceDefaultIfNone(api.WithNamespace(api.NewDefaultContext(), namespace)) + return ctx } func (h *ResizeHandler) isSupportedType(s string) bool { _, ok := supportedResizables[s] return ok } + +func badRequest(msg string, w http.ResponseWriter) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(msg)) +} diff --git a/pkg/apiserver/resthandler.go b/pkg/apiserver/resthandler.go index 9b31701844c32..64121116f85cf 100644 --- a/pkg/apiserver/resthandler.go +++ b/pkg/apiserver/resthandler.go @@ -210,8 +210,8 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt errorJSON(err, h.codec, w) return } - op := h.createOperation(out, sync, timeout, curry(h.setSelfLinkAddName, req)) - h.finishReq(op, req, w) + op := createOperation(h.ops, out, sync, timeout, curry(h.setSelfLinkAddName, req), h.asyncOpWait) + finishReq(op, req, w, h.codec) case "DELETE": if len(parts) != 2 { @@ -223,8 +223,8 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt errorJSON(err, h.codec, w) return } - op := h.createOperation(out, sync, timeout, nil) - h.finishReq(op, req, w) + op := createOperation(h.ops, out, sync, timeout, nil, h.asyncOpWait) + finishReq(op, req, w, h.codec) case "PUT": if len(parts) != 2 { @@ -247,8 +247,8 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt errorJSON(err, h.codec, w) return } - op := h.createOperation(out, sync, timeout, curry(h.setSelfLink, req)) - h.finishReq(op, req, w) + op := createOperation(h.ops, out, sync, timeout, curry(h.setSelfLink, req), h.asyncOpWait) + finishReq(op, req, w, h.codec) default: notFound(w, req) @@ -256,19 +256,19 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt } // createOperation creates an operation to process a channel response. -func (h *RESTHandler) createOperation(out <-chan RESTResult, sync bool, timeout time.Duration, onReceive func(RESTResult)) *Operation { - op := h.ops.NewOperation(out, onReceive) +func createOperation(ops *Operations, out <-chan RESTResult, sync bool, timeout time.Duration, onReceive func(RESTResult), asyncOpWait time.Duration) *Operation { + op := ops.NewOperation(out, onReceive) if sync { op.WaitFor(timeout) - } else if h.asyncOpWait != 0 { - op.WaitFor(h.asyncOpWait) + } else if asyncOpWait != 0 { + op.WaitFor(asyncOpWait) } return op } // finishReq finishes up a request, waiting until the operation finishes or, after a timeout, creating an // Operation to receive the result and returning its ID down the writer. -func (h *RESTHandler) finishReq(op *Operation, req *http.Request, w http.ResponseWriter) { +func finishReq(op *Operation, req *http.Request, w http.ResponseWriter, codec runtime.Codec) { result, complete := op.StatusOrResult() obj := result.Object if complete { @@ -282,8 +282,8 @@ func (h *RESTHandler) finishReq(op *Operation, req *http.Request, w http.Respons status = stat.Code } } - writeJSON(status, h.codec, obj, w) + writeJSON(status, codec, obj, w) } else { - writeJSON(http.StatusAccepted, h.codec, obj, w) + writeJSON(http.StatusAccepted, codec, obj, w) } }