Merge pull request #2282 from bgrant0607/docgen

Automatic API generation via go-restful
This commit is contained in:
Daniel Smith 2014-11-14 13:12:25 -08:00
commit 9430bb38b8
18 changed files with 408 additions and 142 deletions

View File

@ -105,7 +105,7 @@ func NewInvalid(kind, name string, errs ValidationErrorList) error {
} }
return &statusError{api.Status{ return &statusError{api.Status{
Status: api.StatusFailure, Status: api.StatusFailure,
Code: 422, // RFC 4918 Code: 422, // RFC 4918: StatusUnprocessableEntity
Reason: api.StatusReasonInvalid, Reason: api.StatusReasonInvalid,
Details: &api.StatusDetails{ Details: &api.StatusDetails{
Kind: kind, Kind: kind,
@ -121,7 +121,7 @@ func NewBadRequest(reason string) error {
return &statusError{ return &statusError{
api.Status{ api.Status{
Status: api.StatusFailure, Status: api.StatusFailure,
Code: 400, Code: http.StatusBadRequest,
Reason: api.StatusReasonBadRequest, Reason: api.StatusReasonBadRequest,
Details: &api.StatusDetails{ Details: &api.StatusDetails{
Causes: []api.StatusCause{ Causes: []api.StatusCause{
@ -136,7 +136,7 @@ func NewBadRequest(reason string) error {
func NewInternalError(err error) error { func NewInternalError(err error) error {
return &statusError{api.Status{ return &statusError{api.Status{
Status: api.StatusFailure, Status: api.StatusFailure,
Code: 500, Code: http.StatusInternalServerError,
Reason: api.StatusReasonInternalError, Reason: api.StatusReasonInternalError,
Details: &api.StatusDetails{ Details: &api.StatusDetails{
Causes: []api.StatusCause{{Message: err.Error()}}, Causes: []api.StatusCause{{Message: err.Error()}},

View File

@ -20,6 +20,8 @@ import (
"strings" "strings"
) )
// TODO: Address these per #1502
func IsPullAlways(p PullPolicy) bool { func IsPullAlways(p PullPolicy) bool {
return pullPoliciesEqual(p, PullAlways) return pullPoliciesEqual(p, PullAlways)
} }

26
pkg/api/unversioned.go Normal file
View File

@ -0,0 +1,26 @@
/*
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.
*/
package api
// This file contains API types that are unversioned.
// APIVersions lists the api versions that are available, to allow
// version negotiation. APIVersions isn't just an unnamed array of
// strings in order to allow for future evolution, though unversioned
type APIVersions struct {
Versions []string `json:"versions" yaml:"versions"`
}

View File

@ -20,13 +20,17 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"path"
"reflect"
"strings" "strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version" "github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/emicklei/go-restful"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -39,7 +43,7 @@ type Mux interface {
// defaultAPIServer exposes nested objects for testability. // defaultAPIServer exposes nested objects for testability.
type defaultAPIServer struct { type defaultAPIServer struct {
http.Handler http.Handler
group *APIGroup group *APIGroupVersion
} }
const ( const (
@ -49,32 +53,36 @@ const (
// Handle returns a Handler function that exposes the provided storage interfaces // Handle returns a Handler function that exposes the provided storage interfaces
// as RESTful resources at prefix, serialized by codec, and also includes the support // as RESTful resources at prefix, serialized by codec, and also includes the support
// http resources. // http resources.
func Handle(storage map[string]RESTStorage, codec runtime.Codec, prefix string, selfLinker runtime.SelfLinker) http.Handler { func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker) http.Handler {
group := NewAPIGroup(storage, codec, prefix, selfLinker) prefix := root + "/" + version
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker)
mux := http.NewServeMux() container := restful.NewContainer()
group.InstallREST(mux, prefix) mux := container.ServeMux
InstallSupport(mux) group.InstallREST(container, root, version)
ws := new(restful.WebService)
InstallSupport(container, ws)
container.Add(ws)
return &defaultAPIServer{mux, group} return &defaultAPIServer{mux, group}
} }
// APIGroup is a http.Handler that exposes multiple RESTStorage objects // TODO: This is a whole API version right now. Maybe should rename it.
// APIGroupVersion is a http.Handler that exposes multiple RESTStorage objects
// It handles URLs of the form: // It handles URLs of the form:
// /${storage_key}[/${object_name}] // /${storage_key}[/${object_name}]
// Where 'storage_key' points to a RESTStorage object stored in storage. // Where '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. // TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
type APIGroup struct { type APIGroupVersion struct {
handler RESTHandler handler RESTHandler
} }
// NewAPIGroup returns an object that will serve a set of REST resources and their // NewAPIGroupVersion returns an object that will serve a set of REST resources and their
// associated operations. The provided codec controls serialization and deserialization. // associated operations. The provided codec controls serialization and deserialization.
// This is a helper method for registering multiple sets of REST handlers under different // This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server. // prefixes onto a server.
// TODO: add multitype codec serialization // TODO: add multitype codec serialization
func NewAPIGroup(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker) *APIGroup { func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker) *APIGroupVersion {
return &APIGroup{RESTHandler{ return &APIGroupVersion{RESTHandler{
storage: storage, storage: storage,
codec: codec, codec: codec,
canonicalPrefix: canonicalPrefix, canonicalPrefix: canonicalPrefix,
@ -85,70 +93,196 @@ func NewAPIGroup(storage map[string]RESTStorage, codec runtime.Codec, canonicalP
}} }}
} }
func InstallValidator(mux Mux, servers map[string]Server) { // This magic incantation returns *ptrToObject for an arbitrary pointer
validator, err := NewValidator(servers) func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
if err != nil { return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
glog.Errorf("failed to set up validator: %v", err)
return
}
mux.Handle("/validate", validator)
} }
// InstallREST registers the REST handlers (storage, watch, and operations) into a mux. func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, kinds map[string]reflect.Type, h restful.RouteFunction) {
// It is expected that the provided prefix will serve all operations. Path MUST NOT end glog.V(3).Infof("Installing /%s/%s\n", version, path)
// in a slash. object := storage.New()
func (g *APIGroup) InstallREST(mux Mux, paths ...string) { _, kind, err := api.Scheme.ObjectVersionAndKind(object)
if err != nil {
glog.Warningf("error getting kind: %v\n", err)
return
}
versionedPtr, err := api.Scheme.New(version, kind)
if err != nil {
glog.Warningf("error making object: %v\n", err)
return
}
versionedObject := indirectArbitraryPointer(versionedPtr)
glog.V(3).Infoln("type: ", reflect.TypeOf(versionedObject))
// See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic
// and status-code behavior
ws.Route(ws.POST(path).To(h).
Doc("create a " + kind).
Operation("create" + kind).
Reads(versionedObject)) // from the request
// 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)
} 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(ws.GET(path).To(h).
Doc("list objects of kind "+kind).
Operation("list"+kind).
Returns(http.StatusOK, "OK", versionedList))
}
}
ws.Route(ws.GET(path + "/{name}").To(h).
Doc("read the specified " + kind).
Operation("read" + kind).
Param(ws.PathParameter("name", "name of the "+kind).DataType("string")).
Writes(versionedObject)) // on the response
ws.Route(ws.PUT(path + "/{name}").To(h).
Doc("update the specified " + kind).
Operation("update" + kind).
Param(ws.PathParameter("name", "name of the "+kind).DataType("string")).
Reads(versionedObject)) // from the request
// TODO: Support PATCH
ws.Route(ws.DELETE(path + "/{name}").To(h).
Doc("delete the specified " + kind).
Operation("delete" + kind).
Param(ws.PathParameter("name", "name of the "+kind).DataType("string")))
}
// InstallREST registers the REST handlers (storage, watch, and operations) into a restful Container.
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
// in a slash. A restful WebService is created for the group and version.
func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, version string) {
prefix := path.Join(root, version)
restHandler := &g.handler restHandler := &g.handler
strippedHandler := http.StripPrefix(prefix, restHandler)
watchHandler := &WatchHandler{ watchHandler := &WatchHandler{
storage: g.handler.storage, storage: g.handler.storage,
codec: g.handler.codec, codec: g.handler.codec,
canonicalPrefix: g.handler.canonicalPrefix, canonicalPrefix: g.handler.canonicalPrefix,
selfLinker: g.handler.selfLinker, selfLinker: g.handler.selfLinker,
} }
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec}
redirectHandler := &RedirectHandler{g.handler.storage, g.handler.codec} redirectHandler := &RedirectHandler{g.handler.storage, g.handler.codec}
opHandler := &OperationHandler{g.handler.ops, g.handler.codec} opHandler := &OperationHandler{g.handler.ops, g.handler.codec}
for _, prefix := range paths { // Create a new WebService for this APIGroupVersion at the specified path prefix
prefix = strings.TrimRight(prefix, "/") // TODO: Pass in more descriptive documentation
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec} ws := new(restful.WebService)
mux.Handle(prefix+"/", http.StripPrefix(prefix, restHandler)) ws.Path(prefix)
// Note: update GetAttribs() when adding a handler. ws.Doc("API at " + root + ", version " + version)
mux.Handle(prefix+"/watch/", http.StripPrefix(prefix+"/watch/", watchHandler)) // TODO: change to restful.MIME_JSON when we convert YAML->JSON and set content type in client
mux.Handle(prefix+"/proxy/", http.StripPrefix(prefix+"/proxy/", proxyHandler)) ws.Consumes("*/*")
mux.Handle(prefix+"/redirect/", http.StripPrefix(prefix+"/redirect/", redirectHandler)) ws.Produces(restful.MIME_JSON)
mux.Handle(prefix+"/operations", http.StripPrefix(prefix+"/operations", opHandler)) // TODO: require json on input
mux.Handle(prefix+"/operations/", http.StripPrefix(prefix+"/operations/", opHandler)) //ws.Consumes(restful.MIME_JSON)
// TODO: add scheme to APIGroupVersion rather than using api.Scheme
kinds := api.Scheme.KnownTypes(version)
glog.V(4).Infof("InstallREST: %v kinds: %#v", version, kinds)
// TODO: #2057: Return API resources on "/".
// TODO: Add status documentation using Returns()
// Errors (see api/errors/errors.go as well as go-restful router):
// http.StatusNotFound, http.StatusMethodNotAllowed,
// http.StatusUnsupportedMediaType, http.StatusNotAcceptable,
// http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden,
// http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed,
// 422 (StatusUnprocessableEntity), http.StatusInternalServerError,
// http.StatusServiceUnavailable
// and api error codes
// Note that if we specify a versioned Status object here, we may need to
// create one for the tests, also
// Success:
// http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent
//
// test/integration/auth_test.go is currently the most comprehensive status code test
// TODO: eliminate all the restful wrappers
// TODO: create a separate handler per verb
h := func(req *restful.Request, resp *restful.Response) {
glog.V(4).Infof("User-Agent: %s\n", req.HeaderParameter("User-Agent"))
strippedHandler.ServeHTTP(resp.ResponseWriter, req.Request)
}
for path, storage := range g.handler.storage {
registerResourceHandlers(ws, version, path, storage, kinds, h)
}
// TODO: port the rest of these. Sadly, if we don't, we'll have inconsistent
// API behavior, as well as lack of documentation
mux := container.ServeMux
// Note: update GetAttribs() when adding a handler.
mux.Handle(prefix+"/watch/", http.StripPrefix(prefix+"/watch/", watchHandler))
mux.Handle(prefix+"/proxy/", http.StripPrefix(prefix+"/proxy/", proxyHandler))
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))
container.Add(ws)
}
// TODO: Convert to go-restful
func InstallValidator(mux Mux, servers map[string]Server) {
validator, err := NewValidator(servers)
if err != nil {
glog.Errorf("failed to set up validator: %v", err)
return
}
if validator != nil {
mux.Handle("/validate", validator)
} }
} }
// InstallSupport registers the APIServer support functions into a mux. // TODO: document all handlers
func InstallSupport(mux Mux) { // InstallSupport registers the APIServer support functions
healthz.InstallHandler(mux) func InstallSupport(container *restful.Container, ws *restful.WebService) {
mux.HandleFunc("/version", handleVersion) // TODO: convert healthz to restful and remove container arg
mux.HandleFunc("/", handleIndex) healthz.InstallHandler(container.ServeMux)
ws.Route(ws.GET("/").To(handleIndex))
ws.Route(ws.GET("/version").To(handleVersion))
} }
// InstallLogsSupport registers the APIServer log support function into a mux. // InstallLogsSupport registers the APIServer log support function into a mux.
func InstallLogsSupport(mux Mux) { func InstallLogsSupport(mux Mux) {
// TODO: use restful: ws.Route(ws.GET("/logs/{logpath:*}").To(fileHandler))
// See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go
mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))) mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))))
} }
// handleVersion writes the server's version information. // handleVersion writes the server's version information.
func handleVersion(w http.ResponseWriter, req *http.Request) { func handleVersion(req *restful.Request, resp *restful.Response) {
writeRawJSON(http.StatusOK, version.Get(), w) // TODO: use restful's Response methods
writeRawJSON(http.StatusOK, version.Get(), resp.ResponseWriter)
} }
// APIVersionHandler returns a handler which will list the provided versions as available. // APIVersionHandler returns a handler which will list the provided versions as available.
func APIVersionHandler(versions ...string) http.Handler { func APIVersionHandler(versions ...string) restful.RouteFunction {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return func(req *restful.Request, resp *restful.Response) {
writeRawJSON(http.StatusOK, version.APIVersions{Versions: versions}, w) // TODO: use restful's Response methods
}) writeRawJSON(http.StatusOK, api.APIVersions{Versions: versions}, resp.ResponseWriter)
}
} }
// writeJSON renders an object as JSON to the response. // writeJSON renders an object as JSON to the response.
func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter) { func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter) {
output, err := codec.Encode(object) output, err := codec.Encode(object)
if err != nil { if err != nil {
// Note: If codec is broken, this results in an infinite recursion
errorJSON(err, codec, w) errorJSON(err, codec, w)
return return
} }

View File

@ -32,8 +32,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -45,12 +44,54 @@ func convert(obj runtime.Object) (runtime.Object, error) {
return obj, nil return obj, nil
} }
var codec = testapi.Codec() // This creates a fake API version, similar to api/latest.go
var selfLinker = latest.SelfLinker const testVersion = "version"
var versions = []string{testVersion}
var codec = runtime.CodecFor(api.Scheme, testVersion)
var accessor = meta.NewAccessor()
var versioner runtime.ResourceVersioner = accessor
var selfLinker runtime.SelfLinker = accessor
var mapper meta.RESTMapper
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
switch version {
case testVersion:
return &meta.VersionInterfaces{
Codec: codec,
ObjectConvertor: api.Scheme,
MetadataAccessor: accessor,
}, nil
default:
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(versions, ", "))
}
}
func init() { func init() {
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{}) // Certain API objects are returned regardless of the contents of storage:
api.Scheme.AddKnownTypes(testapi.Version(), &Simple{}, &SimpleList{}) // api.Status is returned in errors
// api.ServerOp/api.ServerOpList are returned by /operations
// "internal" version
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{},
&api.Status{}, &api.ServerOp{}, &api.ServerOpList{})
// "version" version
// TODO: Use versioned api objects?
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{},
&api.Status{}, &api.ServerOp{}, &api.ServerOpList{})
defMapper := meta.NewDefaultRESTMapper(
versions,
func(version string) (*meta.VersionInterfaces, bool) {
interfaces, err := interfacesFor(version)
if err != nil {
return nil, false
}
return interfaces, true
},
)
defMapper.Add(api.Scheme, true, versions...)
mapper = defMapper
} }
type Simple struct { type Simple struct {
@ -204,23 +245,24 @@ func TestNotFound(t *testing.T) {
type T struct { type T struct {
Method string Method string
Path string Path string
Status int
} }
cases := map[string]T{ cases := map[string]T{
"PATCH method": {"PATCH", "/prefix/version/foo"}, "PATCH method": {"PATCH", "/prefix/version/foo", http.StatusMethodNotAllowed},
"GET long prefix": {"GET", "/prefix/"}, "GET long prefix": {"GET", "/prefix/", http.StatusNotFound},
"GET missing storage": {"GET", "/prefix/version/blah"}, "GET missing storage": {"GET", "/prefix/version/blah", http.StatusNotFound},
"GET with extra segment": {"GET", "/prefix/version/foo/bar/baz"}, "GET with extra segment": {"GET", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"POST with extra segment": {"POST", "/prefix/version/foo/bar"}, "POST with extra segment": {"POST", "/prefix/version/foo/bar", http.StatusMethodNotAllowed},
"DELETE without extra segment": {"DELETE", "/prefix/version/foo"}, "DELETE without extra segment": {"DELETE", "/prefix/version/foo", http.StatusMethodNotAllowed},
"DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz"}, "DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"PUT without extra segment": {"PUT", "/prefix/version/foo"}, "PUT without extra segment": {"PUT", "/prefix/version/foo", http.StatusMethodNotAllowed},
"PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz"}, "PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"watch missing storage": {"GET", "/prefix/version/watch/"}, "watch missing storage": {"GET", "/prefix/version/watch/", http.StatusNotFound},
"watch with bad method": {"POST", "/prefix/version/watch/foo/bar"}, "watch with bad method": {"POST", "/prefix/version/watch/foo/bar", http.StatusNotFound},
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": &SimpleRESTStorage{}, "foo": &SimpleRESTStorage{},
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -235,14 +277,14 @@ func TestNotFound(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if response.StatusCode != http.StatusNotFound { if response.StatusCode != v.Status {
t.Errorf("Expected %d for %s (%s), Got %#v", http.StatusNotFound, v, k, response) t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v, k, response)
} }
} }
} }
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
handler := Handle(map[string]RESTStorage{}, codec, "/prefix/version", selfLinker) handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -276,7 +318,7 @@ func TestSimpleList(t *testing.T) {
t: t, t: t,
expectedSet: "/prefix/version/simple", expectedSet: "/prefix/version/simple",
} }
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -299,7 +341,7 @@ func TestErrorList(t *testing.T) {
errors: map[string]error{"list": fmt.Errorf("test Error")}, errors: map[string]error{"list": fmt.Errorf("test Error")},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -309,7 +351,7 @@ func TestErrorList(t *testing.T) {
} }
if resp.StatusCode != http.StatusInternalServerError { if resp.StatusCode != http.StatusInternalServerError {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusInternalServerError, resp)
} }
} }
@ -324,7 +366,7 @@ func TestNonEmptyList(t *testing.T) {
}, },
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -366,7 +408,7 @@ func TestGet(t *testing.T) {
expectedSet: "/prefix/version/simple/id", expectedSet: "/prefix/version/simple/id",
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -391,7 +433,7 @@ func TestGetMissing(t *testing.T) {
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")}, errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -410,7 +452,7 @@ func TestDelete(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -433,7 +475,7 @@ func TestDeleteMissing(t *testing.T) {
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)}, errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -458,7 +500,7 @@ func TestUpdate(t *testing.T) {
t: t, t: t,
expectedSet: "/prefix/version/simple/" + ID, expectedSet: "/prefix/version/simple/" + ID,
} }
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -467,7 +509,8 @@ func TestUpdate(t *testing.T) {
} }
body, err := codec.Encode(item) body, err := codec.Encode(item)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) // The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
} }
client := http.Client{} client := http.Client{}
@ -477,7 +520,7 @@ func TestUpdate(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if simpleStorage.updated.Name != item.Name { if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item) t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
} }
if !selfLinker.called { if !selfLinker.called {
@ -492,7 +535,7 @@ func TestUpdateMissing(t *testing.T) {
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)}, errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -527,7 +570,7 @@ func TestCreate(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", testVersion, selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -570,7 +613,7 @@ func TestCreateNotFound(t *testing.T) {
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092. // See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")}, errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
}, },
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -635,7 +678,7 @@ func TestSyncCreate(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": &storage, "foo": &storage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -708,7 +751,7 @@ func TestAsyncDelayReturnsError(t *testing.T) {
return nil, apierrs.NewAlreadyExists("foo", "bar") return nil, apierrs.NewAlreadyExists("foo", "bar")
}, },
} }
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version", selfLinker) handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = time.Millisecond / 2 handler.(*defaultAPIServer).group.handler.asyncOpWait = time.Millisecond / 2
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -732,7 +775,7 @@ func TestAsyncCreateError(t *testing.T) {
name: "bar", name: "bar",
expectedSet: "/prefix/version/foo/bar", expectedSet: "/prefix/version/foo/bar",
} }
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version", selfLinker) handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -784,7 +827,7 @@ func (*UnregisteredAPIObject) IsAnAPIObject() {}
func TestWriteJSONDecodeError(t *testing.T) { func TestWriteJSONDecodeError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
writeJSON(http.StatusOK, latest.Codec, &UnregisteredAPIObject{"Undecodable"}, w) writeJSON(http.StatusOK, codec, &UnregisteredAPIObject{"Undecodable"}, w)
})) }))
defer server.Close() defer server.Close()
status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError) status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError)
@ -832,7 +875,7 @@ func TestSyncCreateTimeout(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": &storage, "foo": &storage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -864,7 +907,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
} }
handler := CORS( handler := CORS(
Handle(map[string]RESTStorage{}, codec, "/prefix/version", selfLinker), Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker),
allowedOriginRegexps, nil, nil, "true", allowedOriginRegexps, nil, nil, "true",
) )
server := httptest.NewServer(handler) server := httptest.NewServer(handler)

View File

@ -121,6 +121,7 @@ func RecoverPanics(handler http.Handler) http.Handler {
}) })
} }
// TODO: use restful.CrossOriginResourceSharing
// Simple CORS implementation that wraps an http Handler // Simple CORS implementation that wraps an http Handler
// For a more detailed implementation use https://github.com/martini-contrib/cors // For a more detailed implementation use https://github.com/martini-contrib/cors
// or implement CORS at your proxy layer // or implement CORS at your proxy layer

View File

@ -19,16 +19,19 @@ package apiserver
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/emicklei/go-restful"
) )
// handleIndex is the root index page for Kubernetes. // handleIndex is the root index page for Kubernetes.
func handleIndex(w http.ResponseWriter, req *http.Request) { func handleIndex(req *restful.Request, resp *restful.Response) {
if req.URL.Path != "/" && req.URL.Path != "/index.html" { // TODO: use restful's Request/Response methods
notFound(w, req) if req.Request.URL.Path != "/" && req.Request.URL.Path != "/index.html" {
notFound(resp.ResponseWriter, req.Request)
return return
} }
w.WriteHeader(http.StatusOK) resp.ResponseWriter.WriteHeader(http.StatusOK)
// TODO: serve this out of a file? // TODO: serve this out of a file
data := "<html><body>Welcome to Kubernetes</body></html>" data := "<html><body>Welcome to Kubernetes</body></html>"
fmt.Fprint(w, data) fmt.Fprint(resp.ResponseWriter, data)
} }

View File

@ -113,7 +113,7 @@ func TestOperationsList(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", "version", selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -170,7 +170,7 @@ func TestOpGet(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", "version", selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()

View File

@ -165,7 +165,7 @@ func TestProxy(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", "version", selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()

View File

@ -31,7 +31,7 @@ func TestRedirect(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix/version", selfLinker) }, codec, "/prefix", "version", selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()

View File

@ -50,7 +50,7 @@ func TestWatchWebsocket(t *testing.T) {
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work. _ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api/version", selfLinker) }, codec, "/api", "version", selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -104,7 +104,7 @@ func TestWatchHTTP(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api/version", selfLinker) }, codec, "/api", "version", selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -167,7 +167,7 @@ func TestWatchParamParsing(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api/version", selfLinker) }, codec, "/api", "version", selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -239,7 +239,7 @@ func TestWatchProtocolSelection(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api/version", selfLinker) }, codec, "/api", "version", selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
defer server.CloseClientConnections() defer server.CloseClientConnections()

View File

@ -63,7 +63,7 @@ func (c *Client) Services(namespace string) ServiceInterface {
// VersionInterface has a method to retrieve the server version. // VersionInterface has a method to retrieve the server version.
type VersionInterface interface { type VersionInterface interface {
ServerVersion() (*version.Info, error) ServerVersion() (*version.Info, error)
ServerAPIVersions() (*version.APIVersions, error) ServerAPIVersions() (*api.APIVersions, error)
} }
// APIStatus is exposed by errors that can be converted to an api.Status object // APIStatus is exposed by errors that can be converted to an api.Status object
@ -92,12 +92,12 @@ func (c *Client) ServerVersion() (*version.Info, error) {
} }
// ServerAPIVersions retrieves and parses the list of API versions the server supports. // ServerAPIVersions retrieves and parses the list of API versions the server supports.
func (c *Client) ServerAPIVersions() (*version.APIVersions, error) { func (c *Client) ServerAPIVersions() (*api.APIVersions, error) {
body, err := c.Get().AbsPath("/api").Do().Raw() body, err := c.Get().AbsPath("/api").Do().Raw()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var v version.APIVersions var v api.APIVersions
err = json.Unmarshal(body, &v) err = json.Unmarshal(body, &v)
if err != nil { if err != nil {
return nil, fmt.Errorf("Got '%s': %v", string(body), err) return nil, fmt.Errorf("Got '%s': %v", string(body), err)

View File

@ -71,7 +71,7 @@ func (c *Fake) ServerVersion() (*version.Info, error) {
return &versionInfo, nil return &versionInfo, nil
} }
func (c *Fake) ServerAPIVersions() (*version.APIVersions, error) { func (c *Fake) ServerAPIVersions() (*api.APIVersions, error) {
c.Actions = append(c.Actions, FakeAction{Action: "get-apiversions", Value: nil}) c.Actions = append(c.Actions, FakeAction{Action: "get-apiversions", Value: nil})
return &version.APIVersions{Versions: []string{"v1beta1", "v1beta2"}}, nil return &api.APIVersions{Versions: []string{"v1beta1", "v1beta2"}}, nil
} }

View File

@ -20,20 +20,24 @@ import (
"net/http" "net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/emicklei/go-restful"
) )
// handleWhoAmI returns the user-string which this request is authenticated as (if any). // handleWhoAmI returns the user-string which this request is authenticated as (if any).
// Useful for debugging authentication. Always returns HTTP status okay and a human // Useful for debugging authentication. Always returns HTTP status okay and a human
// readable (not intended as API) description of authentication state of request. // readable (not intended as API) description of authentication state of request.
func handleWhoAmI(auth authenticator.Request) func(w http.ResponseWriter, req *http.Request) { func handleWhoAmI(auth authenticator.Request) restful.RouteFunction {
return func(w http.ResponseWriter, req *http.Request) { return func(req *restful.Request, resp *restful.Response) {
// This is supposed to go away, so it's not worth the effort to convert to restful
w := resp.ResponseWriter
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
if auth == nil { if auth == nil {
w.Write([]byte("NO AUTHENTICATION SUPPORT")) w.Write([]byte("NO AUTHENTICATION SUPPORT"))
return return
} }
userInfo, ok, err := auth.AuthenticateRequest(req) userInfo, ok, err := auth.AuthenticateRequest(req.Request)
if err != nil { if err != nil {
w.Write([]byte("ERROR WHILE AUTHENTICATING")) w.Write([]byte("ERROR WHILE AUTHENTICATING"))
return return

View File

@ -17,10 +17,13 @@ limitations under the License.
package master package master
import ( import (
"bytes"
_ "expvar"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
rt "runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -51,6 +54,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/ui" "github.com/GoogleCloudPlatform/kubernetes/pkg/ui"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -64,7 +69,6 @@ type Config struct {
MinionRegexp string MinionRegexp string
KubeletClient client.KubeletClient KubeletClient client.KubeletClient
PortalNet *net.IPNet PortalNet *net.IPNet
Mux apiserver.Mux
EnableLogsSupport bool EnableLogsSupport bool
EnableUISupport bool EnableUISupport bool
APIPrefix string APIPrefix string
@ -101,6 +105,8 @@ type Master struct {
client *client.Client client *client.Client
portalNet *net.IPNet portalNet *net.IPNet
mux apiserver.Mux mux apiserver.Mux
handlerContainer *restful.Container
rootWebService *restful.WebService
enableLogsSupport bool enableLogsSupport bool
enableUISupport bool enableUISupport bool
apiPrefix string apiPrefix string
@ -218,6 +224,7 @@ func New(c *Config) *Master {
if c.KubeletClient == nil { if c.KubeletClient == nil {
glog.Fatalf("master.New() called with config.KubeletClient == nil") glog.Fatalf("master.New() called with config.KubeletClient == nil")
} }
mx := http.NewServeMux()
m := &Master{ m := &Master{
podRegistry: etcd.NewRegistry(c.EtcdHelper, boundPodFactory), podRegistry: etcd.NewRegistry(c.EtcdHelper, boundPodFactory),
controllerRegistry: etcd.NewRegistry(c.EtcdHelper, nil), controllerRegistry: etcd.NewRegistry(c.EtcdHelper, nil),
@ -228,7 +235,9 @@ func New(c *Config) *Master {
minionRegistry: minionRegistry, minionRegistry: minionRegistry,
client: c.Client, client: c.Client,
portalNet: c.PortalNet, portalNet: c.PortalNet,
mux: http.NewServeMux(), mux: mx,
handlerContainer: NewHandlerContainer(mx),
rootWebService: new(restful.WebService),
enableLogsSupport: c.EnableLogsSupport, enableLogsSupport: c.EnableLogsSupport,
enableUISupport: c.EnableUISupport, enableUISupport: c.EnableUISupport,
apiPrefix: c.APIPrefix, apiPrefix: c.APIPrefix,
@ -253,6 +262,7 @@ func (m *Master) HandleWithAuth(pattern string, handler http.Handler) {
// URLs into attributes that an Authorizer can understand, and have // URLs into attributes that an Authorizer can understand, and have
// sensible policy defaults for plugged-in endpoints. This will be different // sensible policy defaults for plugged-in endpoints. This will be different
// for generic endpoints versus REST object endpoints. // for generic endpoints versus REST object endpoints.
// TODO: convert to go-restful
m.mux.Handle(pattern, handler) m.mux.Handle(pattern, handler)
} }
@ -260,9 +270,31 @@ func (m *Master) HandleWithAuth(pattern string, handler http.Handler) {
// Applies the same authentication and authorization (if any is configured) // Applies the same authentication and authorization (if any is configured)
// to the request is used for the master's built-in endpoints. // to the request is used for the master's built-in endpoints.
func (m *Master) HandleFuncWithAuth(pattern string, handler func(http.ResponseWriter, *http.Request)) { func (m *Master) HandleFuncWithAuth(pattern string, handler func(http.ResponseWriter, *http.Request)) {
// TODO: convert to go-restful
m.mux.HandleFunc(pattern, handler) m.mux.HandleFunc(pattern, handler)
} }
func NewHandlerContainer(mux *http.ServeMux) *restful.Container {
container := restful.NewContainer()
container.ServeMux = mux
container.RecoverHandler(logStackOnRecover)
return container
}
//TODO: Unify with RecoverPanics?
func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
for i := 2; ; i += 1 {
_, file, line, ok := rt.Caller(i)
if !ok {
break
}
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
}
glog.Errorln(buffer.String())
}
func makeMinionRegistry(c *Config) minion.Registry { func makeMinionRegistry(c *Config) minion.Registry {
var minionRegistry minion.Registry = etcd.NewRegistry(c.EtcdHelper, nil) var minionRegistry minion.Registry = etcd.NewRegistry(c.EtcdHelper, nil)
if c.HealthCheckMinions { if c.HealthCheckMinions {
@ -286,6 +318,7 @@ func (m *Master) init(c *Config) {
authenticator = bearertoken.New(tokenAuthenticator) authenticator = bearertoken.New(tokenAuthenticator)
} }
// TODO: Factor out the core API registration
m.storage = map[string]apiserver.RESTStorage{ m.storage = map[string]apiserver.RESTStorage{
"pods": pod.NewREST(&pod.RESTConfig{ "pods": pod.NewREST(&pod.RESTConfig{
CloudProvider: c.Cloud, CloudProvider: c.Cloud,
@ -304,13 +337,17 @@ func (m *Master) init(c *Config) {
"bindings": binding.NewREST(m.bindingRegistry), "bindings": binding.NewREST(m.bindingRegistry),
} }
apiserver.NewAPIGroup(m.API_v1beta1()).InstallREST(m.mux, c.APIPrefix+"/v1beta1") apiserver.NewAPIGroupVersion(m.API_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1")
apiserver.NewAPIGroup(m.API_v1beta2()).InstallREST(m.mux, c.APIPrefix+"/v1beta2") apiserver.NewAPIGroupVersion(m.API_v1beta2()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta2")
versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
m.mux.Handle(c.APIPrefix, versionHandler)
apiserver.InstallSupport(m.mux)
serversToValidate := m.getServersToValidate(c)
// TODO: InstallREST should register each version automatically
versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
m.rootWebService.Route(m.rootWebService.GET(c.APIPrefix).To(versionHandler))
apiserver.InstallSupport(m.handlerContainer, m.rootWebService)
// TODO: use go-restful
serversToValidate := m.getServersToValidate(c)
apiserver.InstallValidator(m.mux, serversToValidate) apiserver.InstallValidator(m.mux, serversToValidate)
if c.EnableLogsSupport { if c.EnableLogsSupport {
apiserver.InstallLogsSupport(m.mux) apiserver.InstallLogsSupport(m.mux)
@ -319,8 +356,15 @@ func (m *Master) init(c *Config) {
ui.InstallSupport(m.mux) ui.InstallSupport(m.mux)
} }
// TODO: install runtime/pprof handler
// See github.com/emicklei/go-restful/blob/master/examples/restful-cpuprofiler-service.go
handler := http.Handler(m.mux.(*http.ServeMux)) handler := http.Handler(m.mux.(*http.ServeMux))
// TODO: handle CORS and auth using go-restful
// See github.com/emicklei/go-restful/blob/master/examples/restful-CORS-filter.go, and
// github.com/emicklei/go-restful/blob/master/examples/restful-basic-authentication.go
if len(c.CorsAllowedOriginList) > 0 { if len(c.CorsAllowedOriginList) > 0 {
allowedOriginRegexps, err := util.CompileRegexps(c.CorsAllowedOriginList) allowedOriginRegexps, err := util.CompileRegexps(c.CorsAllowedOriginList)
if err != nil { if err != nil {
@ -338,7 +382,23 @@ func (m *Master) init(c *Config) {
if authenticator != nil { if authenticator != nil {
handler = handlers.NewRequestAuthenticator(userContexts, authenticator, handlers.Unauthorized, handler) handler = handlers.NewRequestAuthenticator(userContexts, authenticator, handlers.Unauthorized, handler)
} }
m.mux.HandleFunc("/_whoami", handleWhoAmI(authenticator)) // TODO: Remove temporary _whoami handler
m.rootWebService.Route(m.rootWebService.GET("/_whoami").To(handleWhoAmI(authenticator)))
// Install root web services
m.handlerContainer.Add(m.rootWebService)
// TODO: Make this optional?
// Enable swagger UI and discovery API
swaggerConfig := swagger.Config{
WebServices: m.handlerContainer.RegisteredWebServices(),
// TODO: Parameterize the path?
ApiPath: "/swaggerapi/",
// TODO: Distribute UI javascript and enable the UI
//SwaggerPath: "/swaggerui/",
//SwaggerFilePath: "/srv/apiserver/swagger/dist"
}
swagger.RegisterSwaggerService(swaggerConfig, m.handlerContainer)
m.Handler = handler m.Handler = handler

View File

@ -96,11 +96,8 @@ func RunApiServer(cl *client.Client, etcdClient tools.EtcdClient, addr string, p
ReadOnlyPort: port, ReadOnlyPort: port,
PublicAddress: addr, PublicAddress: addr,
}) })
mux := http.NewServeMux()
apiserver.NewAPIGroup(m.API_v1beta1()).InstallREST(mux, "/api/v1beta1") handler.delegate = m.InsecureHandler
apiserver.NewAPIGroup(m.API_v1beta2()).InstallREST(mux, "/api/v1beta2")
apiserver.InstallSupport(mux)
handler.delegate = mux
go http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), &handler) go http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), &handler)
} }

View File

@ -45,9 +45,3 @@ func Get() Info {
func (info Info) String() string { func (info Info) String() string {
return info.GitVersion return info.GitVersion
} }
// APIVersions lists the api versions that are available, to allow
// version negotiation.
type APIVersions struct {
Versions []string `json:"versions" yaml:"versions"`
}

View File

@ -242,6 +242,7 @@ var code200 = map[int]bool{200: true}
var code400 = map[int]bool{400: true} var code400 = map[int]bool{400: true}
var code403 = map[int]bool{403: true} var code403 = map[int]bool{403: true}
var code404 = map[int]bool{404: true} var code404 = map[int]bool{404: true}
var code405 = map[int]bool{405: true}
var code409 = map[int]bool{409: true} var code409 = map[int]bool{409: true}
var code422 = map[int]bool{422: true} var code422 = map[int]bool{422: true}
var code500 = map[int]bool{500: true} var code500 = map[int]bool{500: true}
@ -269,14 +270,14 @@ func getTestRequests() []struct {
// Non-standard methods (not expected to work, // Non-standard methods (not expected to work,
// but expected to pass/fail authorization prior to // but expected to pass/fail authorization prior to
// failing validation. // failing validation.
{"PATCH", "/api/v1beta1/pods/a", "", code404}, {"PATCH", "/api/v1beta1/pods/a", "", code405},
{"OPTIONS", "/api/v1beta1/pods", "", code404}, {"OPTIONS", "/api/v1beta1/pods", "", code405},
{"OPTIONS", "/api/v1beta1/pods/a", "", code404}, {"OPTIONS", "/api/v1beta1/pods/a", "", code405},
{"HEAD", "/api/v1beta1/pods", "", code404}, {"HEAD", "/api/v1beta1/pods", "", code405},
{"HEAD", "/api/v1beta1/pods/a", "", code404}, {"HEAD", "/api/v1beta1/pods/a", "", code405},
{"TRACE", "/api/v1beta1/pods", "", code404}, {"TRACE", "/api/v1beta1/pods", "", code405},
{"TRACE", "/api/v1beta1/pods/a", "", code404}, {"TRACE", "/api/v1beta1/pods/a", "", code405},
{"NOSUCHVERB", "/api/v1beta1/pods", "", code404}, {"NOSUCHVERB", "/api/v1beta1/pods", "", code405},
// Normal methods on services // Normal methods on services
{"GET", "/api/v1beta1/services", "", code200}, {"GET", "/api/v1beta1/services", "", code200},
@ -320,12 +321,12 @@ func getTestRequests() []struct {
{"DELETE", "/api/v1beta1/events/a" + syncFlags, "", code200}, {"DELETE", "/api/v1beta1/events/a" + syncFlags, "", code200},
// Normal methods on bindings // Normal methods on bindings
{"GET", "/api/v1beta1/bindings", "", code404}, // Bindings are write-only, so 404 {"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/pods" + syncFlags, aPod, code200}, // Need a pod to bind or you get a 404
{"POST", "/api/v1beta1/bindings" + syncFlags, aBinding, code200}, {"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, code500}, // See #2114 about why 500
{"GET", "/api/v1beta1/bindings", "", code404}, {"GET", "/api/v1beta1/bindings", "", code405},
{"GET", "/api/v1beta1/bindings/a", "", code404}, {"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances
{"DELETE", "/api/v1beta1/bindings/a" + syncFlags, "", code404}, {"DELETE", "/api/v1beta1/bindings/a" + syncFlags, "", code404},
// Non-existent object type. // Non-existent object type.
@ -340,7 +341,8 @@ func getTestRequests() []struct {
{"GET", "/api/v1beta1/operations", "", code200}, {"GET", "/api/v1beta1/operations", "", code200},
{"GET", "/api/v1beta1/operations/1234567890", "", code404}, {"GET", "/api/v1beta1/operations/1234567890", "", code404},
// Special verbs on pods // Special verbs on nodes
// TODO: Will become 405 once these are converted to go-restful
{"GET", "/api/v1beta1/proxy/minions/a", "", code404}, {"GET", "/api/v1beta1/proxy/minions/a", "", code404},
{"GET", "/api/v1beta1/redirect/minions/a", "", code404}, {"GET", "/api/v1beta1/redirect/minions/a", "", code404},
// TODO: test .../watch/..., which doesn't end before the test timeout. // TODO: test .../watch/..., which doesn't end before the test timeout.