2014-06-06 23:40:48 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
2014-06-16 05:34:16 +00:00
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
package apiserver
|
|
|
|
|
|
|
|
import (
|
2014-07-29 21:35:20 +00:00
|
|
|
"encoding/json"
|
2014-06-06 23:40:48 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2014-08-06 17:06:42 +00:00
|
|
|
"path"
|
2014-06-17 21:59:37 +00:00
|
|
|
"runtime/debug"
|
2014-06-06 23:40:48 +00:00
|
|
|
"strings"
|
2014-06-19 04:04:11 +00:00
|
|
|
"time"
|
2014-06-17 01:03:44 +00:00
|
|
|
|
2014-06-20 23:18:36 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2014-07-15 22:38:56 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
2014-06-17 01:03:44 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
2014-07-25 19:28:20 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
2014-06-25 03:51:57 +00:00
|
|
|
"github.com/golang/glog"
|
2014-06-06 23:40:48 +00:00
|
|
|
)
|
|
|
|
|
2014-08-06 03:10:48 +00:00
|
|
|
// Codec defines methods for serializing and deserializing API
|
|
|
|
// objects
|
|
|
|
type Codec interface {
|
|
|
|
Encode(obj interface{}) (data []byte, err error)
|
|
|
|
Decode(data []byte) (interface{}, error)
|
|
|
|
DecodeInto(data []byte, obj interface{}) error
|
|
|
|
}
|
|
|
|
|
2014-07-08 07:09:16 +00:00
|
|
|
// APIServer is an HTTPHandler that delegates to RESTStorage objects.
|
2014-06-06 23:40:48 +00:00
|
|
|
// It handles URLs of the form:
|
|
|
|
// ${prefix}/${storage_key}[/${object_name}]
|
|
|
|
// Where 'prefix' is an arbitrary string, and 'storage_key' points to a RESTStorage object stored in storage.
|
|
|
|
//
|
|
|
|
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
2014-07-08 07:09:16 +00:00
|
|
|
type APIServer struct {
|
2014-07-29 13:40:40 +00:00
|
|
|
storage map[string]RESTStorage
|
2014-08-06 03:10:48 +00:00
|
|
|
codec Codec
|
2014-07-29 13:40:40 +00:00
|
|
|
ops *Operations
|
|
|
|
asyncOpWait time.Duration
|
2014-08-06 17:06:42 +00:00
|
|
|
handler http.Handler
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-08-06 03:10:48 +00:00
|
|
|
// New creates a new APIServer object. 'storage' contains a map of handlers. 'codec'
|
|
|
|
// is an interface for decoding to and from JSON. 'prefix' is the hosting path prefix.
|
|
|
|
//
|
|
|
|
// The codec will be used to decode the request body into an object pointer returned by
|
|
|
|
// RESTStorage.New(). The Create() and Update() methods should cast their argument to
|
|
|
|
// the type returned by New().
|
|
|
|
// TODO: add multitype codec serialization
|
|
|
|
func New(storage map[string]RESTStorage, codec Codec, prefix string) *APIServer {
|
2014-07-15 22:38:56 +00:00
|
|
|
s := &APIServer{
|
2014-08-06 03:10:48 +00:00
|
|
|
storage: storage,
|
|
|
|
codec: codec,
|
2014-07-15 22:38:56 +00:00
|
|
|
ops: NewOperations(),
|
2014-07-29 13:40:40 +00:00
|
|
|
// Delay just long enough to handle most simple write operations
|
|
|
|
asyncOpWait: time.Millisecond * 25,
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-07-15 22:38:56 +00:00
|
|
|
|
2014-08-06 17:06:42 +00:00
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
|
|
|
prefix = strings.TrimRight(prefix, "/")
|
|
|
|
|
|
|
|
// Primary API handlers
|
|
|
|
restPrefix := prefix + "/"
|
|
|
|
mux.Handle(restPrefix, http.StripPrefix(restPrefix, http.HandlerFunc(s.handleREST)))
|
|
|
|
|
|
|
|
// Watch API handlers
|
|
|
|
watchPrefix := path.Join(prefix, "watch") + "/"
|
2014-07-31 18:26:34 +00:00
|
|
|
mux.Handle(watchPrefix, http.StripPrefix(watchPrefix, &WatchHandler{storage, codec}))
|
2014-07-22 20:09:22 +00:00
|
|
|
|
2014-07-29 21:36:41 +00:00
|
|
|
// Support services for the apiserver
|
2014-08-06 17:06:42 +00:00
|
|
|
logsPrefix := "/logs/"
|
|
|
|
mux.Handle(logsPrefix, http.StripPrefix(logsPrefix, http.FileServer(http.Dir("/var/log/"))))
|
|
|
|
healthz.InstallHandler(mux)
|
|
|
|
mux.HandleFunc("/version", handleVersion)
|
|
|
|
mux.HandleFunc("/", handleIndex)
|
2014-07-15 22:38:56 +00:00
|
|
|
|
|
|
|
// Handle both operations and operations/* with the same handler
|
2014-08-06 17:06:42 +00:00
|
|
|
handler := &OperationHandler{s.ops, s.codec}
|
|
|
|
operationPrefix := path.Join(prefix, "operations")
|
|
|
|
mux.Handle(operationPrefix, http.StripPrefix(operationPrefix, handler))
|
|
|
|
operationsPrefix := operationPrefix + "/"
|
|
|
|
mux.Handle(operationsPrefix, http.StripPrefix(operationsPrefix, handler))
|
2014-07-17 17:05:14 +00:00
|
|
|
|
2014-07-29 21:36:41 +00:00
|
|
|
// Proxy minion requests
|
2014-08-06 17:06:42 +00:00
|
|
|
mux.Handle("/proxy/minion/", http.StripPrefix("/proxy/minion", http.HandlerFunc(handleProxyMinion)))
|
|
|
|
|
|
|
|
s.handler = mux
|
2014-07-15 07:04:30 +00:00
|
|
|
|
2014-07-15 22:38:56 +00:00
|
|
|
return s
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-07-29 21:36:41 +00:00
|
|
|
// ServeHTTP implements the standard net/http interface.
|
2014-07-29 22:08:22 +00:00
|
|
|
func (s *APIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
2014-06-13 21:58:08 +00:00
|
|
|
defer func() {
|
|
|
|
if x := recover(); x != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
2014-07-29 21:36:41 +00:00
|
|
|
fmt.Fprint(w, "apis panic. Look in log for details.")
|
2014-07-08 07:09:16 +00:00
|
|
|
glog.Infof("APIServer panic'd on %v %v: %#v\n%s\n", req.Method, req.RequestURI, x, debug.Stack())
|
2014-06-13 21:58:08 +00:00
|
|
|
}
|
|
|
|
}()
|
2014-07-15 22:38:56 +00:00
|
|
|
defer httplog.MakeLogged(req, &w).StacktraceWhen(
|
|
|
|
httplog.StatusIsNot(
|
2014-07-02 20:51:27 +00:00
|
|
|
http.StatusOK,
|
|
|
|
http.StatusAccepted,
|
|
|
|
http.StatusConflict,
|
2014-07-18 16:36:27 +00:00
|
|
|
http.StatusNotFound,
|
2014-07-02 20:51:27 +00:00
|
|
|
),
|
|
|
|
).Log()
|
2014-07-15 22:38:56 +00:00
|
|
|
|
2014-08-06 17:06:42 +00:00
|
|
|
// Dispatch to the internal handler
|
|
|
|
s.handler.ServeHTTP(w, req)
|
2014-07-15 22:38:56 +00:00
|
|
|
}
|
|
|
|
|
2014-07-29 22:13:02 +00:00
|
|
|
// handleREST handles requests to all our RESTStorage objects.
|
|
|
|
func (s *APIServer) handleREST(w http.ResponseWriter, req *http.Request) {
|
2014-08-06 17:06:42 +00:00
|
|
|
parts := splitPath(req.URL.Path)
|
|
|
|
if len(parts) < 1 {
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-08-06 17:06:42 +00:00
|
|
|
storage := s.storage[parts[0]]
|
2014-06-06 23:40:48 +00:00
|
|
|
if storage == nil {
|
2014-08-06 17:06:42 +00:00
|
|
|
httplog.LogOf(w).Addf("'%v' has no storage object", parts[0])
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-08 07:09:16 +00:00
|
|
|
|
2014-08-06 17:06:42 +00:00
|
|
|
s.handleRESTStorage(parts, req, w, storage)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-07-29 22:13:02 +00:00
|
|
|
// handleRESTStorage is the main dispatcher for a storage object. It switches on the HTTP method, and then
|
2014-06-16 05:34:16 +00:00
|
|
|
// on path length, according to the following table:
|
|
|
|
// Method Path Action
|
|
|
|
// GET /foo list
|
|
|
|
// GET /foo/bar get 'bar'
|
|
|
|
// POST /foo create
|
|
|
|
// PUT /foo/bar update 'bar'
|
|
|
|
// DELETE /foo/bar delete 'bar'
|
|
|
|
// Returns 404 if the method/pattern doesn't match one of these entries
|
2014-07-29 22:08:22 +00:00
|
|
|
// The s accepts several query parameters:
|
2014-06-19 04:04:11 +00:00
|
|
|
// 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
|
2014-07-29 22:13:02 +00:00
|
|
|
func (s *APIServer) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
|
2014-07-15 22:38:56 +00:00
|
|
|
sync := req.URL.Query().Get("sync") == "true"
|
|
|
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
2014-06-06 23:40:48 +00:00
|
|
|
switch req.Method {
|
|
|
|
case "GET":
|
|
|
|
switch len(parts) {
|
|
|
|
case 1:
|
2014-07-15 22:38:56 +00:00
|
|
|
selector, err := labels.ParseSelector(req.URL.Query().Get("labels"))
|
2014-06-17 00:49:50 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-17 00:49:50 +00:00
|
|
|
return
|
|
|
|
}
|
2014-06-25 20:21:32 +00:00
|
|
|
list, err := storage.List(selector)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-08-06 03:10:48 +00:00
|
|
|
writeJSON(http.StatusOK, s.codec, list, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
case 2:
|
2014-06-17 00:49:50 +00:00
|
|
|
item, err := storage.Get(parts[1])
|
2014-07-16 05:27:15 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-08-06 03:10:48 +00:00
|
|
|
writeJSON(http.StatusOK, s.codec, item, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
default:
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-07-29 21:36:41 +00:00
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
case "POST":
|
|
|
|
if len(parts) != 1 {
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-29 22:10:29 +00:00
|
|
|
body, err := readBody(req)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-08-06 03:10:48 +00:00
|
|
|
obj := storage.New()
|
|
|
|
err = s.codec.DecodeInto(body, obj)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-06-19 04:04:11 +00:00
|
|
|
out, err := storage.Create(obj)
|
2014-06-17 17:50:42 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-19 04:04:11 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-29 21:36:41 +00:00
|
|
|
op := s.createOperation(out, sync, timeout)
|
|
|
|
s.finishReq(op, w)
|
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
case "DELETE":
|
|
|
|
if len(parts) != 2 {
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-06-19 04:04:11 +00:00
|
|
|
out, err := storage.Delete(parts[1])
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-29 21:36:41 +00:00
|
|
|
op := s.createOperation(out, sync, timeout)
|
|
|
|
s.finishReq(op, w)
|
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
case "PUT":
|
|
|
|
if len(parts) != 2 {
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-29 22:10:29 +00:00
|
|
|
body, err := readBody(req)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-07-18 16:36:27 +00:00
|
|
|
return
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-08-06 03:10:48 +00:00
|
|
|
obj := storage.New()
|
|
|
|
err = s.codec.DecodeInto(body, obj)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-06-19 04:04:11 +00:00
|
|
|
out, err := storage.Update(obj)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, s.codec, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-29 21:36:41 +00:00
|
|
|
op := s.createOperation(out, sync, timeout)
|
|
|
|
s.finishReq(op, w)
|
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
default:
|
2014-07-29 21:35:33 +00:00
|
|
|
notFound(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-25 20:21:32 +00:00
|
|
|
|
2014-07-29 21:36:41 +00:00
|
|
|
// handleVersionReq writes the server's version information.
|
|
|
|
func handleVersion(w http.ResponseWriter, req *http.Request) {
|
|
|
|
writeRawJSON(http.StatusOK, version.Get(), w)
|
|
|
|
}
|
|
|
|
|
|
|
|
// createOperation creates an operation to process a channel response
|
|
|
|
func (s *APIServer) createOperation(out <-chan interface{}, sync bool, timeout time.Duration) *Operation {
|
|
|
|
op := s.ops.NewOperation(out)
|
|
|
|
if sync {
|
|
|
|
op.WaitFor(timeout)
|
2014-07-29 13:40:40 +00:00
|
|
|
} else if s.asyncOpWait != 0 {
|
|
|
|
op.WaitFor(s.asyncOpWait)
|
2014-07-29 21:36:41 +00:00
|
|
|
}
|
|
|
|
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 (s *APIServer) finishReq(op *Operation, w http.ResponseWriter) {
|
|
|
|
obj, complete := op.StatusOrResult()
|
|
|
|
if complete {
|
|
|
|
status := http.StatusOK
|
|
|
|
switch stat := obj.(type) {
|
|
|
|
case api.Status:
|
|
|
|
httplog.LogOf(w).Addf("programmer error: use *api.Status as a result, not api.Status.")
|
|
|
|
if stat.Code != 0 {
|
|
|
|
status = stat.Code
|
|
|
|
}
|
|
|
|
case *api.Status:
|
|
|
|
if stat.Code != 0 {
|
|
|
|
status = stat.Code
|
|
|
|
}
|
|
|
|
}
|
2014-08-06 03:10:48 +00:00
|
|
|
writeJSON(status, s.codec, obj, w)
|
2014-07-29 21:36:41 +00:00
|
|
|
} else {
|
2014-08-06 03:10:48 +00:00
|
|
|
writeJSON(http.StatusAccepted, s.codec, obj, w)
|
2014-07-29 21:36:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeJSON renders an object as JSON to the response
|
2014-08-06 03:10:48 +00:00
|
|
|
func writeJSON(statusCode int, codec Codec, object interface{}, w http.ResponseWriter) {
|
|
|
|
output, err := codec.Encode(object)
|
2014-07-29 22:14:00 +00:00
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
errorJSON(err, codec, w)
|
2014-07-29 22:14:00 +00:00
|
|
|
return
|
|
|
|
}
|
2014-07-29 22:54:20 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(statusCode)
|
2014-07-29 22:14:00 +00:00
|
|
|
w.Write(output)
|
|
|
|
}
|
|
|
|
|
2014-07-31 18:26:34 +00:00
|
|
|
// errorJSON renders an error to the response
|
|
|
|
func errorJSON(err error, codec Codec, w http.ResponseWriter) {
|
|
|
|
status := errToAPIStatus(err)
|
|
|
|
writeJSON(status.Code, codec, status, w)
|
|
|
|
}
|
|
|
|
|
2014-07-29 22:14:00 +00:00
|
|
|
// writeRawJSON writes a non-API object in JSON.
|
|
|
|
func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
|
|
|
|
output, err := json.Marshal(object)
|
|
|
|
if err != nil {
|
2014-07-31 18:26:34 +00:00
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
2014-07-29 22:14:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
w.Write(output)
|
|
|
|
}
|
|
|
|
|
2014-07-29 22:13:02 +00:00
|
|
|
func parseTimeout(str string) time.Duration {
|
|
|
|
if str != "" {
|
|
|
|
timeout, err := time.ParseDuration(str)
|
|
|
|
if err == nil {
|
|
|
|
return timeout
|
|
|
|
}
|
|
|
|
glog.Errorf("Failed to parse: %#v '%s'", err, str)
|
|
|
|
}
|
|
|
|
return 30 * time.Second
|
|
|
|
}
|
|
|
|
|
2014-07-29 22:10:29 +00:00
|
|
|
func readBody(req *http.Request) ([]byte, error) {
|
|
|
|
defer req.Body.Close()
|
|
|
|
return ioutil.ReadAll(req.Body)
|
|
|
|
}
|
2014-08-06 17:06:42 +00:00
|
|
|
|
|
|
|
// splitPath returns the segments for a URL path
|
|
|
|
func splitPath(path string) []string {
|
|
|
|
path = strings.Trim(path, "/")
|
|
|
|
if path == "" {
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
return strings.Split(path, "/")
|
|
|
|
}
|