diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 934aca0698d09..3744de3045d2c 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -178,6 +178,14 @@ 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} + 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 // TODO: Pass in more descriptive documentation ws := new(restful.WebService) @@ -233,6 +241,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..17bc9a6ed95a3 --- /dev/null +++ b/pkg/apiserver/resize.go @@ -0,0 +1,98 @@ +package apiserver + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "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 + ops *Operations + timeout time.Duration +} + +var supportedResizables = map[string]bool{ + "replicationControllers": true, +} + +const ( + paramInc string = "inc" + paramNamespace string = "namespace" + urlDelimiter string = "/" +) + +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) + return + } + + resizableType := pathParts[0] + resizableName := pathParts[1] + ctx := h.contextFromRequest(req) + inc := req.URL.Query().Get(paramInc) + + if !h.isSupportedType(resizableType) { + badRequest("The requested resource does not support resizing", w) + 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) + + //shouldn't ever get here if the supported types are correct, but check anyway + if !ok { + badRequest("The requested resource does not support resizing", w) + return + } + + 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) 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) } } 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() {