Automatic API generation by adopting go-restful

This commit is contained in:
Brian Grant
2014-11-11 07:11:45 +00:00
parent dd29cd8353
commit 7583e1a643
18 changed files with 408 additions and 142 deletions

View File

@@ -20,13 +20,17 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"path"
"reflect"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
)
@@ -39,7 +43,7 @@ type Mux interface {
// defaultAPIServer exposes nested objects for testability.
type defaultAPIServer struct {
http.Handler
group *APIGroup
group *APIGroupVersion
}
const (
@@ -49,32 +53,36 @@ const (
// Handle returns a Handler function that exposes the provided storage interfaces
// as RESTful resources at prefix, serialized by codec, and also includes the support
// http resources.
func Handle(storage map[string]RESTStorage, codec runtime.Codec, prefix string, selfLinker runtime.SelfLinker) http.Handler {
group := NewAPIGroup(storage, codec, prefix, selfLinker)
mux := http.NewServeMux()
group.InstallREST(mux, prefix)
InstallSupport(mux)
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker) http.Handler {
prefix := root + "/" + version
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker)
container := restful.NewContainer()
mux := container.ServeMux
group.InstallREST(container, root, version)
ws := new(restful.WebService)
InstallSupport(container, ws)
container.Add(ws)
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:
// /${storage_key}[/${object_name}]
// 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.
type APIGroup struct {
type APIGroupVersion struct {
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.
// This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server.
// TODO: add multitype codec serialization
func NewAPIGroup(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker) *APIGroup {
return &APIGroup{RESTHandler{
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker) *APIGroupVersion {
return &APIGroupVersion{RESTHandler{
storage: storage,
codec: codec,
canonicalPrefix: canonicalPrefix,
@@ -85,70 +93,196 @@ func NewAPIGroup(storage map[string]RESTStorage, codec runtime.Codec, canonicalP
}}
}
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
}
mux.Handle("/validate", validator)
// This magic incantation returns *ptrToObject for an arbitrary pointer
func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
}
// InstallREST registers the REST handlers (storage, watch, and operations) into a mux.
// It is expected that the provided prefix will serve all operations. Path MUST NOT end
// in a slash.
func (g *APIGroup) InstallREST(mux Mux, paths ...string) {
func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, kinds map[string]reflect.Type, h restful.RouteFunction) {
glog.V(3).Infof("Installing /%s/%s\n", version, path)
object := storage.New()
_, 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
strippedHandler := http.StripPrefix(prefix, restHandler)
watchHandler := &WatchHandler{
storage: g.handler.storage,
codec: g.handler.codec,
canonicalPrefix: g.handler.canonicalPrefix,
selfLinker: g.handler.selfLinker,
}
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec}
redirectHandler := &RedirectHandler{g.handler.storage, g.handler.codec}
opHandler := &OperationHandler{g.handler.ops, g.handler.codec}
for _, prefix := range paths {
prefix = strings.TrimRight(prefix, "/")
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec}
mux.Handle(prefix+"/", http.StripPrefix(prefix, restHandler))
// 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))
// Create a new WebService for this APIGroupVersion at the specified path prefix
// TODO: Pass in more descriptive documentation
ws := new(restful.WebService)
ws.Path(prefix)
ws.Doc("API at " + root + ", version " + version)
// TODO: change to restful.MIME_JSON when we convert YAML->JSON and set content type in client
ws.Consumes("*/*")
ws.Produces(restful.MIME_JSON)
// TODO: require json on input
//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.
func InstallSupport(mux Mux) {
healthz.InstallHandler(mux)
mux.HandleFunc("/version", handleVersion)
mux.HandleFunc("/", handleIndex)
// TODO: document all handlers
// InstallSupport registers the APIServer support functions
func InstallSupport(container *restful.Container, ws *restful.WebService) {
// TODO: convert healthz to restful and remove container arg
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.
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/"))))
}
// handleVersion writes the server's version information.
func handleVersion(w http.ResponseWriter, req *http.Request) {
writeRawJSON(http.StatusOK, version.Get(), w)
func handleVersion(req *restful.Request, resp *restful.Response) {
// 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.
func APIVersionHandler(versions ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
writeRawJSON(http.StatusOK, version.APIVersions{Versions: versions}, w)
})
func APIVersionHandler(versions ...string) restful.RouteFunction {
return func(req *restful.Request, resp *restful.Response) {
// TODO: use restful's Response methods
writeRawJSON(http.StatusOK, api.APIVersions{Versions: versions}, resp.ResponseWriter)
}
}
// writeJSON renders an object as JSON to the response.
func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter) {
output, err := codec.Encode(object)
if err != nil {
// Note: If codec is broken, this results in an infinite recursion
errorJSON(err, codec, w)
return
}

View File

@@ -32,8 +32,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@@ -45,12 +44,54 @@ func convert(obj runtime.Object) (runtime.Object, error) {
return obj, nil
}
var codec = testapi.Codec()
var selfLinker = latest.SelfLinker
// This creates a fake API version, similar to api/latest.go
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() {
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{})
api.Scheme.AddKnownTypes(testapi.Version(), &Simple{}, &SimpleList{})
// Certain API objects are returned regardless of the contents of storage:
// 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 {
@@ -204,23 +245,24 @@ func TestNotFound(t *testing.T) {
type T struct {
Method string
Path string
Status int
}
cases := map[string]T{
"PATCH method": {"PATCH", "/prefix/version/foo"},
"GET long prefix": {"GET", "/prefix/"},
"GET missing storage": {"GET", "/prefix/version/blah"},
"GET with extra segment": {"GET", "/prefix/version/foo/bar/baz"},
"POST with extra segment": {"POST", "/prefix/version/foo/bar"},
"DELETE without extra segment": {"DELETE", "/prefix/version/foo"},
"DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz"},
"PUT without extra segment": {"PUT", "/prefix/version/foo"},
"PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz"},
"watch missing storage": {"GET", "/prefix/version/watch/"},
"watch with bad method": {"POST", "/prefix/version/watch/foo/bar"},
"PATCH method": {"PATCH", "/prefix/version/foo", http.StatusMethodNotAllowed},
"GET long prefix": {"GET", "/prefix/", http.StatusNotFound},
"GET missing storage": {"GET", "/prefix/version/blah", http.StatusNotFound},
"GET with extra segment": {"GET", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"POST with extra segment": {"POST", "/prefix/version/foo/bar", http.StatusMethodNotAllowed},
"DELETE without extra segment": {"DELETE", "/prefix/version/foo", http.StatusMethodNotAllowed},
"DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"PUT without extra segment": {"PUT", "/prefix/version/foo", http.StatusMethodNotAllowed},
"PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"watch missing storage": {"GET", "/prefix/version/watch/", http.StatusNotFound},
"watch with bad method": {"POST", "/prefix/version/watch/foo/bar", http.StatusNotFound},
}
handler := Handle(map[string]RESTStorage{
"foo": &SimpleRESTStorage{},
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -235,14 +277,14 @@ func TestNotFound(t *testing.T) {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusNotFound {
t.Errorf("Expected %d for %s (%s), Got %#v", http.StatusNotFound, v, k, response)
if response.StatusCode != v.Status {
t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v, k, response)
}
}
}
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)
defer server.Close()
client := http.Client{}
@@ -276,7 +318,7 @@ func TestSimpleList(t *testing.T) {
t: t,
expectedSet: "/prefix/version/simple",
}
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -299,7 +341,7 @@ func TestErrorList(t *testing.T) {
errors: map[string]error{"list": fmt.Errorf("test Error")},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -309,7 +351,7 @@ func TestErrorList(t *testing.T) {
}
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
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -366,7 +408,7 @@ func TestGet(t *testing.T) {
expectedSet: "/prefix/version/simple/id",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -391,7 +433,7 @@ func TestGetMissing(t *testing.T) {
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -410,7 +452,7 @@ func TestDelete(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -433,7 +475,7 @@ func TestDeleteMissing(t *testing.T) {
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -458,7 +500,7 @@ func TestUpdate(t *testing.T) {
t: t,
expectedSet: "/prefix/version/simple/" + ID,
}
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -467,7 +509,8 @@ func TestUpdate(t *testing.T) {
}
body, err := codec.Encode(item)
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{}
@@ -477,7 +520,7 @@ func TestUpdate(t *testing.T) {
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)
}
if !selfLinker.called {
@@ -492,7 +535,7 @@ func TestUpdateMissing(t *testing.T) {
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix/version", selfLinker)
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -527,7 +570,7 @@ func TestCreate(t *testing.T) {
}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", testVersion, selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler)
defer server.Close()
@@ -570,7 +613,7 @@ func TestCreateNotFound(t *testing.T) {
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
},
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -635,7 +678,7 @@ func TestSyncCreate(t *testing.T) {
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -708,7 +751,7 @@ func TestAsyncDelayReturnsError(t *testing.T) {
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
server := httptest.NewServer(handler)
defer server.Close()
@@ -732,7 +775,7 @@ func TestAsyncCreateError(t *testing.T) {
name: "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
server := httptest.NewServer(handler)
defer server.Close()
@@ -784,7 +827,7 @@ func (*UnregisteredAPIObject) IsAnAPIObject() {}
func TestWriteJSONDecodeError(t *testing.T) {
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()
status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError)
@@ -832,7 +875,7 @@ func TestSyncCreateTimeout(t *testing.T) {
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", testVersion, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -864,7 +907,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
}
handler := CORS(
Handle(map[string]RESTStorage{}, codec, "/prefix/version", selfLinker),
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker),
allowedOriginRegexps, nil, nil, "true",
)
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
// For a more detailed implementation use https://github.com/martini-contrib/cors
// or implement CORS at your proxy layer

View File

@@ -19,16 +19,19 @@ package apiserver
import (
"fmt"
"net/http"
"github.com/emicklei/go-restful"
)
// handleIndex is the root index page for Kubernetes.
func handleIndex(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" && req.URL.Path != "/index.html" {
notFound(w, req)
func handleIndex(req *restful.Request, resp *restful.Response) {
// TODO: use restful's Request/Response methods
if req.Request.URL.Path != "/" && req.Request.URL.Path != "/index.html" {
notFound(resp.ResponseWriter, req.Request)
return
}
w.WriteHeader(http.StatusOK)
// TODO: serve this out of a file?
resp.ResponseWriter.WriteHeader(http.StatusOK)
// TODO: serve this out of a file
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{
"foo": simpleStorage,
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", "version", selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler)
defer server.Close()
@@ -170,7 +170,7 @@ func TestOpGet(t *testing.T) {
}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/prefix/version", selfLinker)
}, codec, "/prefix", "version", selfLinker)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler)
defer server.Close()

View File

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

View File

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

View File

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