mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Automatic API generation by adopting go-restful
This commit is contained in:
		@@ -105,7 +105,7 @@ func NewInvalid(kind, name string, errs ValidationErrorList) error {
 | 
			
		||||
	}
 | 
			
		||||
	return &statusError{api.Status{
 | 
			
		||||
		Status: api.StatusFailure,
 | 
			
		||||
		Code:   422, // RFC 4918
 | 
			
		||||
		Code:   422, // RFC 4918: StatusUnprocessableEntity
 | 
			
		||||
		Reason: api.StatusReasonInvalid,
 | 
			
		||||
		Details: &api.StatusDetails{
 | 
			
		||||
			Kind:   kind,
 | 
			
		||||
@@ -121,7 +121,7 @@ func NewBadRequest(reason string) error {
 | 
			
		||||
	return &statusError{
 | 
			
		||||
		api.Status{
 | 
			
		||||
			Status: api.StatusFailure,
 | 
			
		||||
			Code:   400,
 | 
			
		||||
			Code:   http.StatusBadRequest,
 | 
			
		||||
			Reason: api.StatusReasonBadRequest,
 | 
			
		||||
			Details: &api.StatusDetails{
 | 
			
		||||
				Causes: []api.StatusCause{
 | 
			
		||||
@@ -136,7 +136,7 @@ func NewBadRequest(reason string) error {
 | 
			
		||||
func NewInternalError(err error) error {
 | 
			
		||||
	return &statusError{api.Status{
 | 
			
		||||
		Status: api.StatusFailure,
 | 
			
		||||
		Code:   500,
 | 
			
		||||
		Code:   http.StatusInternalServerError,
 | 
			
		||||
		Reason: api.StatusReasonInternalError,
 | 
			
		||||
		Details: &api.StatusDetails{
 | 
			
		||||
			Causes: []api.StatusCause{{Message: err.Error()}},
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO: Address these per #1502
 | 
			
		||||
 | 
			
		||||
func IsPullAlways(p PullPolicy) bool {
 | 
			
		||||
	// Default to pull always
 | 
			
		||||
	if len(p) == 0 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								pkg/api/unversioned.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pkg/api/unversioned.go
									
									
									
									
									
										Normal 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"`
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ func (c *Client) Services(namespace string) ServiceInterface {
 | 
			
		||||
// VersionInterface has a method to retrieve the server version.
 | 
			
		||||
type VersionInterface interface {
 | 
			
		||||
	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
 | 
			
		||||
@@ -91,12 +91,12 @@ func (c *Client) ServerVersion() (*version.Info, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var v version.APIVersions
 | 
			
		||||
	var v api.APIVersions
 | 
			
		||||
	err = json.Unmarshal(body, &v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Got '%s': %v", string(body), err)
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ func (c *Fake) ServerVersion() (*version.Info, error) {
 | 
			
		||||
	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})
 | 
			
		||||
	return &version.APIVersions{Versions: []string{"v1beta1", "v1beta2"}}, nil
 | 
			
		||||
	return &api.APIVersions{Versions: []string{"v1beta1", "v1beta2"}}, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,20 +20,24 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"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).
 | 
			
		||||
// Useful for debugging authentication.  Always returns HTTP status okay and a human
 | 
			
		||||
// readable (not intended as API) description of authentication state of request.
 | 
			
		||||
func handleWhoAmI(auth authenticator.Request) func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	return func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
func handleWhoAmI(auth authenticator.Request) restful.RouteFunction {
 | 
			
		||||
	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.WriteHeader(http.StatusOK)
 | 
			
		||||
		if auth == nil {
 | 
			
		||||
			w.Write([]byte("NO AUTHENTICATION SUPPORT"))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		userInfo, ok, err := auth.AuthenticateRequest(req)
 | 
			
		||||
		userInfo, ok, err := auth.AuthenticateRequest(req.Request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			w.Write([]byte("ERROR WHILE AUTHENTICATING"))
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,13 @@ limitations under the License.
 | 
			
		||||
package master
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	_ "expvar"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	rt "runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -51,6 +54,8 @@ import (
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/ui"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/emicklei/go-restful"
 | 
			
		||||
	"github.com/emicklei/go-restful/swagger"
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +69,6 @@ type Config struct {
 | 
			
		||||
	MinionRegexp          string
 | 
			
		||||
	KubeletClient         client.KubeletClient
 | 
			
		||||
	PortalNet             *net.IPNet
 | 
			
		||||
	Mux                   apiserver.Mux
 | 
			
		||||
	EnableLogsSupport     bool
 | 
			
		||||
	EnableUISupport       bool
 | 
			
		||||
	APIPrefix             string
 | 
			
		||||
@@ -101,6 +105,8 @@ type Master struct {
 | 
			
		||||
	client                *client.Client
 | 
			
		||||
	portalNet             *net.IPNet
 | 
			
		||||
	mux                   apiserver.Mux
 | 
			
		||||
	handlerContainer      *restful.Container
 | 
			
		||||
	rootWebService        *restful.WebService
 | 
			
		||||
	enableLogsSupport     bool
 | 
			
		||||
	enableUISupport       bool
 | 
			
		||||
	apiPrefix             string
 | 
			
		||||
@@ -218,6 +224,7 @@ func New(c *Config) *Master {
 | 
			
		||||
	if c.KubeletClient == nil {
 | 
			
		||||
		glog.Fatalf("master.New() called with config.KubeletClient == nil")
 | 
			
		||||
	}
 | 
			
		||||
	mx := http.NewServeMux()
 | 
			
		||||
	m := &Master{
 | 
			
		||||
		podRegistry:           etcd.NewRegistry(c.EtcdHelper, boundPodFactory),
 | 
			
		||||
		controllerRegistry:    etcd.NewRegistry(c.EtcdHelper, nil),
 | 
			
		||||
@@ -228,7 +235,9 @@ func New(c *Config) *Master {
 | 
			
		||||
		minionRegistry:        minionRegistry,
 | 
			
		||||
		client:                c.Client,
 | 
			
		||||
		portalNet:             c.PortalNet,
 | 
			
		||||
		mux:                   http.NewServeMux(),
 | 
			
		||||
		mux:                   mx,
 | 
			
		||||
		handlerContainer:      NewHandlerContainer(mx),
 | 
			
		||||
		rootWebService:        new(restful.WebService),
 | 
			
		||||
		enableLogsSupport:     c.EnableLogsSupport,
 | 
			
		||||
		enableUISupport:       c.EnableUISupport,
 | 
			
		||||
		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
 | 
			
		||||
	// sensible policy defaults for plugged-in endpoints.  This will be different
 | 
			
		||||
	// for generic endpoints versus REST object endpoints.
 | 
			
		||||
	// TODO: convert to go-restful
 | 
			
		||||
	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)
 | 
			
		||||
// to the request is used for the master's built-in endpoints.
 | 
			
		||||
func (m *Master) HandleFuncWithAuth(pattern string, handler func(http.ResponseWriter, *http.Request)) {
 | 
			
		||||
	// TODO: convert to go-restful
 | 
			
		||||
	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 {
 | 
			
		||||
	var minionRegistry minion.Registry = etcd.NewRegistry(c.EtcdHelper, nil)
 | 
			
		||||
	if c.HealthCheckMinions {
 | 
			
		||||
@@ -286,6 +318,7 @@ func (m *Master) init(c *Config) {
 | 
			
		||||
		authenticator = bearertoken.New(tokenAuthenticator)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: Factor out the core API registration
 | 
			
		||||
	m.storage = map[string]apiserver.RESTStorage{
 | 
			
		||||
		"pods": pod.NewREST(&pod.RESTConfig{
 | 
			
		||||
			CloudProvider: c.Cloud,
 | 
			
		||||
@@ -304,13 +337,17 @@ func (m *Master) init(c *Config) {
 | 
			
		||||
		"bindings": binding.NewREST(m.bindingRegistry),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiserver.NewAPIGroup(m.API_v1beta1()).InstallREST(m.mux, c.APIPrefix+"/v1beta1")
 | 
			
		||||
	apiserver.NewAPIGroup(m.API_v1beta2()).InstallREST(m.mux, c.APIPrefix+"/v1beta2")
 | 
			
		||||
	versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
 | 
			
		||||
	m.mux.Handle(c.APIPrefix, versionHandler)
 | 
			
		||||
	apiserver.InstallSupport(m.mux)
 | 
			
		||||
	serversToValidate := m.getServersToValidate(c)
 | 
			
		||||
	apiserver.NewAPIGroupVersion(m.API_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1")
 | 
			
		||||
	apiserver.NewAPIGroupVersion(m.API_v1beta2()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta2")
 | 
			
		||||
 | 
			
		||||
	// 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)
 | 
			
		||||
	if c.EnableLogsSupport {
 | 
			
		||||
		apiserver.InstallLogsSupport(m.mux)
 | 
			
		||||
@@ -319,8 +356,15 @@ func (m *Master) init(c *Config) {
 | 
			
		||||
		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))
 | 
			
		||||
 | 
			
		||||
	// 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 {
 | 
			
		||||
		allowedOriginRegexps, err := util.CompileRegexps(c.CorsAllowedOriginList)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -338,7 +382,23 @@ func (m *Master) init(c *Config) {
 | 
			
		||||
	if authenticator != nil {
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -96,11 +96,8 @@ func RunApiServer(cl *client.Client, etcdClient tools.EtcdClient, addr string, p
 | 
			
		||||
		ReadOnlyPort:  port,
 | 
			
		||||
		PublicAddress: addr,
 | 
			
		||||
	})
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
	apiserver.NewAPIGroup(m.API_v1beta1()).InstallREST(mux, "/api/v1beta1")
 | 
			
		||||
	apiserver.NewAPIGroup(m.API_v1beta2()).InstallREST(mux, "/api/v1beta2")
 | 
			
		||||
	apiserver.InstallSupport(mux)
 | 
			
		||||
	handler.delegate = mux
 | 
			
		||||
 | 
			
		||||
	handler.delegate = m.InsecureHandler
 | 
			
		||||
 | 
			
		||||
	go http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), &handler)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,3 @@ func Get() Info {
 | 
			
		||||
func (info Info) String() string {
 | 
			
		||||
	return info.GitVersion
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// APIVersions lists the api versions that are available, to allow
 | 
			
		||||
// version negotiation.
 | 
			
		||||
type APIVersions struct {
 | 
			
		||||
	Versions []string `json:"versions" yaml:"versions"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -242,6 +242,7 @@ var code200 = map[int]bool{200: true}
 | 
			
		||||
var code400 = map[int]bool{400: true}
 | 
			
		||||
var code403 = map[int]bool{403: true}
 | 
			
		||||
var code404 = map[int]bool{404: true}
 | 
			
		||||
var code405 = map[int]bool{405: true}
 | 
			
		||||
var code409 = map[int]bool{409: true}
 | 
			
		||||
var code422 = map[int]bool{422: true}
 | 
			
		||||
var code500 = map[int]bool{500: true}
 | 
			
		||||
@@ -269,14 +270,14 @@ func getTestRequests() []struct {
 | 
			
		||||
		// Non-standard methods (not expected to work,
 | 
			
		||||
		// but expected to pass/fail authorization prior to
 | 
			
		||||
		// failing validation.
 | 
			
		||||
		{"PATCH", "/api/v1beta1/pods/a", "", code404},
 | 
			
		||||
		{"OPTIONS", "/api/v1beta1/pods", "", code404},
 | 
			
		||||
		{"OPTIONS", "/api/v1beta1/pods/a", "", code404},
 | 
			
		||||
		{"HEAD", "/api/v1beta1/pods", "", code404},
 | 
			
		||||
		{"HEAD", "/api/v1beta1/pods/a", "", code404},
 | 
			
		||||
		{"TRACE", "/api/v1beta1/pods", "", code404},
 | 
			
		||||
		{"TRACE", "/api/v1beta1/pods/a", "", code404},
 | 
			
		||||
		{"NOSUCHVERB", "/api/v1beta1/pods", "", code404},
 | 
			
		||||
		{"PATCH", "/api/v1beta1/pods/a", "", code405},
 | 
			
		||||
		{"OPTIONS", "/api/v1beta1/pods", "", code405},
 | 
			
		||||
		{"OPTIONS", "/api/v1beta1/pods/a", "", code405},
 | 
			
		||||
		{"HEAD", "/api/v1beta1/pods", "", code405},
 | 
			
		||||
		{"HEAD", "/api/v1beta1/pods/a", "", code405},
 | 
			
		||||
		{"TRACE", "/api/v1beta1/pods", "", code405},
 | 
			
		||||
		{"TRACE", "/api/v1beta1/pods/a", "", code405},
 | 
			
		||||
		{"NOSUCHVERB", "/api/v1beta1/pods", "", code405},
 | 
			
		||||
 | 
			
		||||
		// Normal methods on services
 | 
			
		||||
		{"GET", "/api/v1beta1/services", "", code200},
 | 
			
		||||
@@ -320,12 +321,12 @@ func getTestRequests() []struct {
 | 
			
		||||
		{"DELETE", "/api/v1beta1/events/a" + syncFlags, "", code200},
 | 
			
		||||
 | 
			
		||||
		// 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/bindings" + syncFlags, aBinding, code200},
 | 
			
		||||
		{"PUT", "/api/v1beta1/bindings/a" + syncFlags, aBinding, code500}, // See #2114 about why 500
 | 
			
		||||
		{"GET", "/api/v1beta1/bindings", "", code404},
 | 
			
		||||
		{"GET", "/api/v1beta1/bindings/a", "", code404},
 | 
			
		||||
		{"GET", "/api/v1beta1/bindings", "", code405},
 | 
			
		||||
		{"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances
 | 
			
		||||
		{"DELETE", "/api/v1beta1/bindings/a" + syncFlags, "", code404},
 | 
			
		||||
 | 
			
		||||
		// Non-existent object type.
 | 
			
		||||
@@ -340,7 +341,8 @@ func getTestRequests() []struct {
 | 
			
		||||
		{"GET", "/api/v1beta1/operations", "", code200},
 | 
			
		||||
		{"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/redirect/minions/a", "", code404},
 | 
			
		||||
		// TODO: test .../watch/..., which doesn't end before the test timeout.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user