Merge pull request #5054 from smarterclayton/switch_to_sub_bindings

Make Pod binding a subresource (and be generic to other types)
This commit is contained in:
Brian Grant
2015-03-09 16:41:26 -07:00
29 changed files with 555 additions and 521 deletions

View File

@@ -101,6 +101,10 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
j.Spec = api.PodSpec{} j.Spec = api.PodSpec{}
c.Fuzz(&j.Spec) c.Fuzz(&j.Spec)
}, },
func(j *api.Binding, c fuzz.Continue) {
c.Fuzz(&j.ObjectMeta)
j.Target.Name = c.RandString()
},
func(j *api.ReplicationControllerSpec, c fuzz.Continue) { func(j *api.ReplicationControllerSpec, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again c.FuzzNoCustom(j) // fuzz self without calling this function again
j.TemplateRef = nil // this is required for round trip j.TemplateRef = nil // this is required for round trip

View File

@@ -918,13 +918,14 @@ type NamespaceList struct {
Items []Namespace `json:"items"` Items []Namespace `json:"items"`
} }
// Binding is written by a scheduler to cause a pod to be bound to a host. // Binding ties one object to another - for example, a pod is bound to a node by a scheduler.
type Binding struct { type Binding struct {
TypeMeta `json:",inline"` TypeMeta `json:",inline"`
// ObjectMeta describes the object that is being bound.
ObjectMeta `json:"metadata,omitempty"` ObjectMeta `json:"metadata,omitempty"`
PodID string `json:"podID"` // Target is the object to bind to.
Host string `json:"host"` Target ObjectReference `json:"target"`
} }
// Status is a return value for calls that don't return other objects. // Status is a return value for calls that don't return other objects.

View File

@@ -1324,6 +1324,24 @@ func init() {
return nil return nil
}, },
func(in *Binding, out *newer.Binding, s conversion.Scope) error {
if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
return err
}
out.Target = newer.ObjectReference{
Name: in.Host,
}
out.Name = in.PodID
return nil
},
func(in *newer.Binding, out *Binding, s conversion.Scope) error {
if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
return err
}
out.Host = in.Target.Name
out.PodID = in.Name
return nil
},
) )
if err != nil { if err != nil {
// If one of the conversion functions is malformed, detect it immediately. // If one of the conversion functions is malformed, detect it immediately.

View File

@@ -1240,6 +1240,24 @@ func init() {
return nil return nil
}, },
func(in *Binding, out *newer.Binding, s conversion.Scope) error {
if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
return err
}
out.Target = newer.ObjectReference{
Name: in.Host,
}
out.Name = in.PodID
return nil
},
func(in *newer.Binding, out *Binding, s conversion.Scope) error {
if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
return err
}
out.Host = in.Target.Name
out.PodID = in.Name
return nil
},
) )
if err != nil { if err != nil {
// If one of the conversion functions is malformed, detect it immediately. // If one of the conversion functions is malformed, detect it immediately.

View File

@@ -940,16 +940,14 @@ type NamespaceList struct {
Items []Namespace `json:"items" description:"items is the list of Namespace objects in the list"` Items []Namespace `json:"items" description:"items is the list of Namespace objects in the list"`
} }
// Binding is written by a scheduler to cause a pod to be bound to a node. Name is not // Binding ties one object to another - for example, a pod is bound to a node by a scheduler.
// required for Bindings.
type Binding struct { type Binding struct {
TypeMeta `json:",inline"` TypeMeta `json:",inline"`
// ObjectMeta describes the object that is being bound.
ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#metadata"` ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#metadata"`
// PodID is a Pod name to be bound to a node. // Target is the object to bind to.
PodID string `json:"podID" description:"name of the pod to be bound to a node"` Target ObjectReference `json:"target" description:"an object to bind to"`
// Host is the name of a node to bind to.
Host string `json:"host" description:"name of the node to bind to"`
} }
// Status is a return value for calls that don't return other objects. // Status is a return value for calls that don't return other objects.

View File

@@ -26,16 +26,15 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
) )
type APIInstaller struct { type APIInstaller struct {
group *APIGroupVersion group *APIGroupVersion
prefix string // Path prefix where API resources are to be registered. info *APIRequestInfoResolver
version string // The API version being installed. prefix string // Path prefix where API resources are to be registered.
} }
// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc). // Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc).
@@ -58,15 +57,15 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
// Initialize the custom handlers. // Initialize the custom handlers.
watchHandler := (&WatchHandler{ watchHandler := (&WatchHandler{
storage: a.group.storage, storage: a.group.Storage,
codec: a.group.codec, codec: a.group.Codec,
linker: a.group.linker, linker: a.group.Linker,
info: a.group.info, info: a.info,
}) })
redirectHandler := (&RedirectHandler{a.group.storage, a.group.codec, a.group.context, a.group.info}) redirectHandler := (&RedirectHandler{a.group.Storage, a.group.Codec, a.group.Context, a.info})
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.storage, a.group.codec, a.group.context, a.group.info}) proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info})
for path, storage := range a.group.storage { for path, storage := range a.group.Storage {
if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil { if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil {
errors = append(errors, err) errors = append(errors, err)
} }
@@ -77,18 +76,17 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
func (a *APIInstaller) newWebService() *restful.WebService { func (a *APIInstaller) newWebService() *restful.WebService {
ws := new(restful.WebService) ws := new(restful.WebService)
ws.Path(a.prefix) ws.Path(a.prefix)
ws.Doc("API at " + a.prefix + " version " + a.version) ws.Doc("API at " + a.prefix + " version " + a.group.Version)
// TODO: change to restful.MIME_JSON when we set content type in client // TODO: change to restful.MIME_JSON when we set content type in client
ws.Consumes("*/*") ws.Consumes("*/*")
ws.Produces(restful.MIME_JSON) ws.Produces(restful.MIME_JSON)
ws.ApiVersion(a.version) ws.ApiVersion(a.group.Version)
return ws return ws
} }
func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler, redirectHandler, proxyHandler http.Handler) error { func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler, redirectHandler, proxyHandler http.Handler) error {
codec := a.group.codec admit := a.group.Admit
admit := a.group.admit context := a.group.Context
context := a.group.context
var resource, subresource string var resource, subresource string
switch parts := strings.Split(path, "/"); len(parts) { switch parts := strings.Split(path, "/"); len(parts) {
@@ -97,19 +95,16 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
case 1: case 1:
resource = parts[0] resource = parts[0]
default: default:
// TODO: support deeper paths
return fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)") return fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)")
} }
object := storage.New() object := storage.New()
// TODO: add scheme to APIInstaller rather than using api.Scheme _, kind, err := a.group.Typer.ObjectVersionAndKind(object)
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
if err != nil { if err != nil {
return err return err
} }
versionedPtr, err := api.Scheme.New(a.version, kind) versionedPtr, err := a.group.Creater.New(a.group.Version, kind)
if conversion.IsNotRegisteredError(err) {
return nil
}
if err != nil { if err != nil {
return err return err
} }
@@ -118,15 +113,15 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
var versionedList interface{} var versionedList interface{}
if lister, ok := storage.(RESTLister); ok { if lister, ok := storage.(RESTLister); ok {
list := lister.NewList() list := lister.NewList()
_, listKind, err := api.Scheme.ObjectVersionAndKind(list) _, listKind, err := a.group.Typer.ObjectVersionAndKind(list)
versionedListPtr, err := api.Scheme.New(a.version, listKind) versionedListPtr, err := a.group.Creater.New(a.group.Version, listKind)
if err != nil { if err != nil {
return err return err
} }
versionedList = indirectArbitraryPointer(versionedListPtr) versionedList = indirectArbitraryPointer(versionedListPtr)
} }
mapping, err := a.group.mapper.RESTMapping(kind, a.version) mapping, err := a.group.Mapper.RESTMapping(kind, a.group.Version)
if err != nil { if err != nil {
return err return err
} }
@@ -156,25 +151,27 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
// Get the list of actions for the given scope. // Get the list of actions for the given scope.
if scope.Name() != meta.RESTScopeNameNamespace { if scope.Name() != meta.RESTScopeNameNamespace {
itemPath := resource + "/{name}" resourcePath := resource
itemPath := resourcePath + "/{name}"
if len(subresource) > 0 { if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource itemPath = itemPath + "/" + subresource
resourcePath = itemPath
} }
nameParams := append(params, nameParam) nameParams := append(params, nameParam)
namer := rootScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)} namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)}
// Handler for standard REST verbs (GET, PUT, POST and DELETE). // Handler for standard REST verbs (GET, PUT, POST and DELETE).
actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister) actions = appendIf(actions, action{"LIST", resourcePath, params, namer}, isLister)
actions = appendIf(actions, action{"POST", resource, params, namer}, isCreater) actions = appendIf(actions, action{"POST", resourcePath, params, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, params, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, isWatcher) actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"REDIRECT", "redirect/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
} else { } else {
// v1beta3 format with namespace in path // v1beta3 format with namespace in path
@@ -184,29 +181,31 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + resource namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + resource
namespaceParams := []*restful.Parameter{namespaceParam} namespaceParams := []*restful.Parameter{namespaceParam}
resourcePath := namespacedPath
itemPath := namespacedPath + "/{name}" itemPath := namespacedPath + "/{name}"
if len(subresource) > 0 { if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource itemPath = itemPath + "/" + subresource
resourcePath = itemPath
} }
nameParams := append(namespaceParams, nameParam) nameParams := append(namespaceParams, nameParam)
namer := scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), false} namer := scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), false}
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams, namer}, isLister) actions = appendIf(actions, action{"LIST", resourcePath, namespaceParams, namer}, isLister)
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams, namer}, isCreater) actions = appendIf(actions, action{"POST", resourcePath, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, isWatcher) actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"REDIRECT", "redirect/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
// list across namespace. // list across namespace.
namer = scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), true} namer = scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), true}
actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister) actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer}, allowWatchList)
} else { } else {
// Handler for standard REST verbs (GET, PUT, POST and DELETE). // Handler for standard REST verbs (GET, PUT, POST and DELETE).
@@ -214,24 +213,27 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string") namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
namespaceParams := []*restful.Parameter{namespaceParam} namespaceParams := []*restful.Parameter{namespaceParam}
itemPath := resource + "/{name}" basePath := resource
resourcePath := basePath
itemPath := resourcePath + "/{name}"
if len(subresource) > 0 { if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource itemPath = itemPath + "/" + subresource
resourcePath = itemPath
} }
nameParams := append(namespaceParams, nameParam) nameParams := append(namespaceParams, nameParam)
namer := legacyScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)} namer := legacyScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)}
actions = appendIf(actions, action{"LIST", resource, namespaceParams, namer}, isLister) actions = appendIf(actions, action{"LIST", resourcePath, namespaceParams, namer}, isLister)
actions = appendIf(actions, action{"POST", resource, namespaceParams, namer}, isCreater) actions = appendIf(actions, action{"POST", resourcePath, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, namespaceParams, namer}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, isWatcher) actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"REDIRECT", "redirect/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
} }
} }
@@ -256,7 +258,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
m := monitorFilter(action.Verb, resource) m := monitorFilter(action.Verb, resource)
switch action.Verb { switch action.Verb {
case "GET": // Get a resource. case "GET": // Get a resource.
route := ws.GET(action.Path).To(GetResource(getter, ctxFn, action.Namer, codec)). route := ws.GET(action.Path).To(GetResource(getter, ctxFn, action.Namer, mapping.Codec)).
Filter(m). Filter(m).
Doc("read the specified " + kind). Doc("read the specified " + kind).
Operation("read" + kind). Operation("read" + kind).
@@ -264,7 +266,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params) addParams(route, action.Params)
ws.Route(route) ws.Route(route)
case "LIST": // List all resources of a kind. case "LIST": // List all resources of a kind.
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, codec, a.group.info)). route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, mapping.Codec, a.group.Version, resource)).
Filter(m). Filter(m).
Doc("list objects of kind " + kind). Doc("list objects of kind " + kind).
Operation("list" + kind). Operation("list" + kind).
@@ -272,7 +274,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params) addParams(route, action.Params)
ws.Route(route) ws.Route(route)
case "PUT": // Update a resource. case "PUT": // Update a resource.
route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, action.Namer, codec, resource, admit)). route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, action.Namer, mapping.Codec, a.group.Typer, resource, admit)).
Filter(m). Filter(m).
Doc("replace the specified " + kind). Doc("replace the specified " + kind).
Operation("replace" + kind). Operation("replace" + kind).
@@ -280,7 +282,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params) addParams(route, action.Params)
ws.Route(route) ws.Route(route)
case "POST": // Create a resource. case "POST": // Create a resource.
route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, action.Namer, codec, resource, admit)). route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, action.Namer, mapping.Codec, a.group.Typer, resource, admit)).
Filter(m). Filter(m).
Doc("create a " + kind). Doc("create a " + kind).
Operation("create" + kind). Operation("create" + kind).
@@ -288,7 +290,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params) addParams(route, action.Params)
ws.Route(route) ws.Route(route)
case "DELETE": // Delete a resource. case "DELETE": // Delete a resource.
route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, action.Namer, codec, resource, kind, admit)). route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, action.Namer, mapping.Codec, resource, kind, admit)).
Filter(m). Filter(m).
Doc("delete a " + kind). Doc("delete a " + kind).
Operation("delete" + kind) Operation("delete" + kind)

View File

@@ -29,7 +29,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@@ -91,72 +90,38 @@ type Mux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
} }
// defaultAPIServer exposes nested objects for testability.
type defaultAPIServer struct {
http.Handler
group *APIGroupVersion
}
// 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.
// Note: This method is used only in tests.
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) http.Handler {
prefix := path.Join(root, version)
group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, contextMapper, mapper)
container := restful.NewContainer()
container.Router(restful.CurlyRouter{})
mux := container.ServeMux
group.InstallREST(container, root, version)
ws := new(restful.WebService)
InstallSupport(mux, ws)
container.Add(ws)
return &defaultAPIServer{mux, group}
}
// APIGroupVersion is a helper for exposing RESTStorage objects as http.Handlers via go-restful // APIGroupVersion is a helper for exposing RESTStorage objects as http.Handlers via go-restful
// 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.
type APIGroupVersion struct { type APIGroupVersion struct {
storage map[string]RESTStorage Storage map[string]RESTStorage
codec runtime.Codec
prefix string
linker runtime.SelfLinker
admit admission.Interface
context api.RequestContextMapper
mapper meta.RESTMapper
// TODO: put me into a cleaner interface
info *APIRequestInfoResolver
}
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their Root string
// associated operations. The provided codec controls serialization and deserialization. Version string
// This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server. Mapper meta.RESTMapper
// TODO: add multitype codec serialization
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) *APIGroupVersion { Codec runtime.Codec
return &APIGroupVersion{ Typer runtime.ObjectTyper
storage: storage, Creater runtime.ObjectCreater
codec: codec, Linker runtime.SelfLinker
prefix: prefix,
linker: linker, Admit admission.Interface
admit: admissionControl, Context api.RequestContextMapper
context: contextMapper,
mapper: mapper,
info: &APIRequestInfoResolver{util.NewStringSet(strings.TrimPrefix(root, "/")), latest.RESTMapper},
}
} }
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container. // InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end // 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. // in a slash. A restful WebService is created for the group and version.
func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, version string) error { func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
prefix := path.Join(root, version) info := &APIRequestInfoResolver{util.NewStringSet(strings.TrimPrefix(g.Root, "/")), g.Mapper}
prefix := path.Join(g.Root, g.Version)
installer := &APIInstaller{ installer := &APIInstaller{
group: g, group: g,
prefix: prefix, info: info,
version: version, prefix: prefix,
} }
ws, registrationErrors := installer.Install() ws, registrationErrors := installer.Install()
container.Add(ws) container.Add(ws)

View File

@@ -41,6 +41,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
"github.com/emicklei/go-restful"
) )
func convert(obj runtime.Object) (runtime.Object, error) { func convert(obj runtime.Object) (runtime.Object, error) {
@@ -115,6 +117,59 @@ func init() {
requestContextMapper = api.NewRequestContextMapper() requestContextMapper = api.NewRequestContextMapper()
} }
// defaultAPIServer exposes nested objects for testability.
type defaultAPIServer struct {
http.Handler
group *APIGroupVersion
}
// uses the default settings
func handle(storage map[string]RESTStorage) http.Handler {
return handleInternal(storage, admissionControl, mapper, selfLinker)
}
// tests with a deny admission controller
func handleDeny(storage map[string]RESTStorage) http.Handler {
return handleInternal(storage, deny.NewAlwaysDeny(), mapper, selfLinker)
}
// tests using the new namespace scope mechanism
func handleNamespaced(storage map[string]RESTStorage) http.Handler {
return handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
}
// tests using a custom self linker
func handleLinker(storage map[string]RESTStorage, selfLinker runtime.SelfLinker) http.Handler {
return handleInternal(storage, admissionControl, mapper, selfLinker)
}
func handleInternal(storage map[string]RESTStorage, admissionControl admission.Interface, mapper meta.RESTMapper, selfLinker runtime.SelfLinker) http.Handler {
group := &APIGroupVersion{
Storage: storage,
Mapper: mapper,
Root: "/api",
Version: testVersion,
Creater: api.Scheme,
Typer: api.Scheme,
Codec: codec,
Linker: selfLinker,
Admit: admissionControl,
Context: requestContextMapper,
}
container := restful.NewContainer()
container.Router(restful.CurlyRouter{})
mux := container.ServeMux
group.InstallREST(container)
ws := new(restful.WebService)
InstallSupport(mux, ws)
container.Add(ws)
return &defaultAPIServer{mux, group}
}
type Simple struct { type Simple struct {
api.TypeMeta `json:",inline"` api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata"` api.ObjectMeta `json:"metadata"`
@@ -285,21 +340,21 @@ func TestNotFound(t *testing.T) {
Status int Status int
} }
cases := map[string]T{ cases := map[string]T{
"PATCH method": {"PATCH", "/prefix/version/foo", http.StatusMethodNotAllowed}, "PATCH method": {"PATCH", "/api/version/foo", http.StatusMethodNotAllowed},
"GET long prefix": {"GET", "/prefix/", http.StatusNotFound}, "GET long prefix": {"GET", "/api/", http.StatusNotFound},
"GET missing storage": {"GET", "/prefix/version/blah", http.StatusNotFound}, "GET missing storage": {"GET", "/api/version/blah", http.StatusNotFound},
"GET with extra segment": {"GET", "/prefix/version/foo/bar/baz", http.StatusNotFound}, "GET with extra segment": {"GET", "/api/version/foo/bar/baz", http.StatusNotFound},
"POST with extra segment": {"POST", "/prefix/version/foo/bar", http.StatusMethodNotAllowed}, "POST with extra segment": {"POST", "/api/version/foo/bar", http.StatusMethodNotAllowed},
"DELETE without extra segment": {"DELETE", "/prefix/version/foo", http.StatusMethodNotAllowed}, "DELETE without extra segment": {"DELETE", "/api/version/foo", http.StatusMethodNotAllowed},
"DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz", http.StatusNotFound}, "DELETE with extra segment": {"DELETE", "/api/version/foo/bar/baz", http.StatusNotFound},
"PUT without extra segment": {"PUT", "/prefix/version/foo", http.StatusMethodNotAllowed}, "PUT without extra segment": {"PUT", "/api/version/foo", http.StatusMethodNotAllowed},
"PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz", http.StatusNotFound}, "PUT with extra segment": {"PUT", "/api/version/foo/bar/baz", http.StatusNotFound},
"watch missing storage": {"GET", "/prefix/version/watch/", http.StatusNotFound}, "watch missing storage": {"GET", "/api/version/watch/", http.StatusNotFound},
"watch with bad method": {"POST", "/prefix/version/watch/foo/bar", http.StatusMethodNotAllowed}, "watch with bad method": {"POST", "/api/version/watch/foo/bar", http.StatusMethodNotAllowed},
} }
handler := Handle(map[string]RESTStorage{ handler := handle(map[string]RESTStorage{
"foo": &SimpleRESTStorage{}, "foo": &SimpleRESTStorage{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) })
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -339,19 +394,19 @@ func TestUnimplementedRESTStorage(t *testing.T) {
ErrCode int ErrCode int
} }
cases := map[string]T{ cases := map[string]T{
"GET object": {"GET", "/prefix/version/foo/bar", http.StatusNotFound}, "GET object": {"GET", "/api/version/foo/bar", http.StatusNotFound},
"GET list": {"GET", "/prefix/version/foo", http.StatusNotFound}, "GET list": {"GET", "/api/version/foo", http.StatusNotFound},
"POST list": {"POST", "/prefix/version/foo", http.StatusNotFound}, "POST list": {"POST", "/api/version/foo", http.StatusNotFound},
"PUT object": {"PUT", "/prefix/version/foo/bar", http.StatusNotFound}, "PUT object": {"PUT", "/api/version/foo/bar", http.StatusNotFound},
"DELETE object": {"DELETE", "/prefix/version/foo/bar", http.StatusNotFound}, "DELETE object": {"DELETE", "/api/version/foo/bar", http.StatusNotFound},
"watch list": {"GET", "/prefix/version/watch/foo", http.StatusNotFound}, "watch list": {"GET", "/api/version/watch/foo", http.StatusNotFound},
"watch object": {"GET", "/prefix/version/watch/foo/bar", http.StatusNotFound}, "watch object": {"GET", "/api/version/watch/foo/bar", http.StatusNotFound},
"proxy object": {"GET", "/prefix/version/proxy/foo/bar", http.StatusNotFound}, "proxy object": {"GET", "/api/version/proxy/foo/bar", http.StatusNotFound},
"redirect object": {"GET", "/prefix/version/redirect/foo/bar", http.StatusNotFound}, "redirect object": {"GET", "/api/version/redirect/foo/bar", http.StatusNotFound},
} }
handler := Handle(map[string]RESTStorage{ handler := handle(map[string]RESTStorage{
"foo": UnimplementedRESTStorage{}, "foo": UnimplementedRESTStorage{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) })
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -376,7 +431,7 @@ func TestUnimplementedRESTStorage(t *testing.T) {
} }
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(map[string]RESTStorage{})
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -409,14 +464,14 @@ func TestList(t *testing.T) {
selfLink string selfLink string
legacy bool legacy bool
}{ }{
{"/prefix/version/simple", "", "/prefix/version/simple?namespace=", true}, {"/api/version/simple", "", "/api/version/simple?namespace=", true},
{"/prefix/version/simple?namespace=other", "other", "/prefix/version/simple?namespace=other", true}, {"/api/version/simple?namespace=other", "other", "/api/version/simple?namespace=other", true},
// list items across all namespaces // list items across all namespaces
{"/prefix/version/simple?namespace=", "", "/prefix/version/simple?namespace=", true}, {"/api/version/simple?namespace=", "", "/api/version/simple?namespace=", true},
{"/prefix/version/namespaces/default/simple", "default", "/prefix/version/namespaces/default/simple", false}, {"/api/version/namespaces/default/simple", "default", "/api/version/namespaces/default/simple", false},
{"/prefix/version/namespaces/other/simple", "other", "/prefix/version/namespaces/other/simple", false}, {"/api/version/namespaces/other/simple", "other", "/api/version/namespaces/other/simple", false},
// list items across all namespaces // list items across all namespaces
{"/prefix/version/simple", "", "/prefix/version/simple", false}, {"/api/version/simple", "", "/api/version/simple", false},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
storage := map[string]RESTStorage{} storage := map[string]RESTStorage{}
@@ -429,9 +484,9 @@ func TestList(t *testing.T) {
} }
var handler http.Handler var handler http.Handler
if testCase.legacy { if testCase.legacy {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler = handleLinker(storage, selfLinker)
} else { } else {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper) handler = handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
} }
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -462,11 +517,11 @@ 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", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple") resp, err := http.Get(server.URL + "/api/version/simple")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -487,11 +542,11 @@ func TestNonEmptyList(t *testing.T) {
}, },
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple") resp, err := http.Get(server.URL + "/api/version/simple")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -515,10 +570,10 @@ func TestNonEmptyList(t *testing.T) {
if listOut.Items[0].Other != simpleStorage.list[0].Other { if listOut.Items[0].Other != simpleStorage.list[0].Other {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
} }
if listOut.SelfLink != "/prefix/version/simple?namespace=" { if listOut.SelfLink != "/api/version/simple?namespace=" {
t.Errorf("unexpected list self link: %#v", listOut) t.Errorf("unexpected list self link: %#v", listOut)
} }
expectedSelfLink := "/prefix/version/simple/something?namespace=other" expectedSelfLink := "/api/version/simple/something?namespace=other"
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink { if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink) t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
} }
@@ -535,11 +590,11 @@ func TestSelfLinkSkipsEmptyName(t *testing.T) {
}, },
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple") resp, err := http.Get(server.URL + "/api/version/simple")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -562,7 +617,7 @@ func TestSelfLinkSkipsEmptyName(t *testing.T) {
if listOut.Items[0].Other != simpleStorage.list[0].Other { if listOut.Items[0].Other != simpleStorage.list[0].Other {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
} }
if listOut.SelfLink != "/prefix/version/simple?namespace=" { if listOut.SelfLink != "/api/version/simple?namespace=" {
t.Errorf("unexpected list self link: %#v", listOut) t.Errorf("unexpected list self link: %#v", listOut)
} }
expectedSelfLink := "" expectedSelfLink := ""
@@ -580,16 +635,16 @@ func TestGet(t *testing.T) {
} }
selfLinker := &setTestSelfLinker{ selfLinker := &setTestSelfLinker{
t: t, t: t,
expectedSet: "/prefix/version/simple/id?namespace=default", expectedSet: "/api/version/simple/id?namespace=default",
name: "id", name: "id",
namespace: "default", namespace: "default",
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple/id") resp, err := http.Get(server.URL + "/api/version/simple/id")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -619,16 +674,16 @@ func TestGetAlternateSelfLink(t *testing.T) {
} }
selfLinker := &setTestSelfLinker{ selfLinker := &setTestSelfLinker{
t: t, t: t,
expectedSet: "/prefix/version/simple/id?namespace=test", expectedSet: "/api/version/simple/id?namespace=test",
name: "id", name: "id",
namespace: "test", namespace: "test",
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper) handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple/id?namespace=test") resp, err := http.Get(server.URL + "/api/version/simple/id?namespace=test")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -657,16 +712,16 @@ func TestGetNamespaceSelfLink(t *testing.T) {
} }
selfLinker := &setTestSelfLinker{ selfLinker := &setTestSelfLinker{
t: t, t: t,
expectedSet: "/prefix/version/namespaces/foo/simple/id", expectedSet: "/api/version/namespaces/foo/simple/id",
name: "id", name: "id",
namespace: "foo", namespace: "foo",
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper) handler := handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/namespaces/foo/simple/id") resp, err := http.Get(server.URL + "/api/version/namespaces/foo/simple/id")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -691,11 +746,11 @@ 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", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple/id") resp, err := http.Get(server.URL + "/api/version/simple/id")
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@@ -710,12 +765,12 @@ func TestDelete(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil) request, err := http.NewRequest("DELETE", server.URL+"/api/version/simple/"+ID, nil)
res, err := client.Do(request) res, err := client.Do(request)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@@ -733,12 +788,12 @@ func TestDeleteInvokesAdmissionControl(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) handler := handleDeny(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil) request, err := http.NewRequest("DELETE", server.URL+"/api/version/simple/"+ID, nil)
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -755,12 +810,12 @@ 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", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil) request, err := http.NewRequest("DELETE", server.URL+"/api/version/simple/"+ID, nil)
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -778,11 +833,11 @@ func TestUpdate(t *testing.T) {
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{ selfLinker := &setTestSelfLinker{
t: t, t: t,
expectedSet: "/prefix/version/simple/" + ID + "?namespace=default", expectedSet: "/api/version/simple/" + ID + "?namespace=default",
name: ID, name: ID,
namespace: api.NamespaceDefault, namespace: api.NamespaceDefault,
} }
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -800,7 +855,7 @@ func TestUpdate(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request) _, err = client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -819,7 +874,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) handler := handleDeny(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -837,7 +892,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -852,7 +907,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) handler := handleDeny(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -866,7 +921,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -881,7 +936,7 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -898,7 +953,7 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -918,7 +973,7 @@ func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
t: t, t: t,
err: fmt.Errorf("test error"), err: fmt.Errorf("test error"),
} }
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -936,7 +991,7 @@ func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request) _, err = client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -955,7 +1010,7 @@ func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -973,7 +1028,7 @@ func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -990,7 +1045,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", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(storage)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -1007,7 +1062,7 @@ func TestUpdateMissing(t *testing.T) {
} }
client := http.Client{} client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body)) request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@@ -1018,20 +1073,20 @@ func TestUpdateMissing(t *testing.T) {
} }
func TestCreateNotFound(t *testing.T) { func TestCreateNotFound(t *testing.T) {
handler := Handle(map[string]RESTStorage{ handler := handle(map[string]RESTStorage{
"simple": &SimpleRESTStorage{ "simple": &SimpleRESTStorage{
// storage.Create can fail with not found error in theory. // storage.Create can fail with not found error in theory.
// 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", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) })
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
simple := &Simple{Other: "foo"} simple := &Simple{Other: "foo"}
data, _ := codec.Encode(simple) data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/simple", bytes.NewBuffer(data)) request, err := http.NewRequest("POST", server.URL+"/api/version/simple", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@@ -1046,6 +1101,54 @@ func TestCreateNotFound(t *testing.T) {
} }
} }
func TestCreateChecksDecode(t *testing.T) {
handler := handle(map[string]RESTStorage{"simple": &SimpleRESTStorage{}})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
simple := &api.Pod{}
data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/api/version/simple", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusBadRequest {
t.Errorf("Unexpected response %#v", response)
}
if b, _ := ioutil.ReadAll(response.Body); !strings.Contains(string(b), "must be of type Simple") {
t.Errorf("unexpected response: %s", string(b))
}
}
func TestUpdateChecksDecode(t *testing.T) {
handler := handle(map[string]RESTStorage{"simple": &SimpleRESTStorage{}})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
simple := &api.Pod{}
data, _ := codec.Encode(simple)
request, err := http.NewRequest("PUT", server.URL+"/api/version/simple/bar", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusBadRequest {
t.Errorf("Unexpected response %#v", response)
}
if b, _ := ioutil.ReadAll(response.Body); !strings.Contains(string(b), "must be of type Simple") {
t.Errorf("unexpected response: %s", string(b))
}
}
func TestParseTimeout(t *testing.T) { func TestParseTimeout(t *testing.T) {
if d := parseTimeout(""); d != 30*time.Second { if d := parseTimeout(""); d != 30*time.Second {
t.Errorf("blank timeout produces %v", d) t.Errorf("blank timeout produces %v", d)
@@ -1089,11 +1192,9 @@ func TestCreate(t *testing.T) {
t: t, t: t,
name: "bar", name: "bar",
namespace: "default", namespace: "default",
expectedSet: "/prefix/version/foo/bar?namespace=default", expectedSet: "/api/version/foo/bar?namespace=default",
} }
handler := Handle(map[string]RESTStorage{ handler := handleLinker(map[string]RESTStorage{"foo": &storage}, selfLinker)
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -1102,7 +1203,7 @@ func TestCreate(t *testing.T) {
Other: "bar", Other: "bar",
} }
data, _ := codec.Encode(simple) data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data)) request, err := http.NewRequest("POST", server.URL+"/api/version/foo", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@@ -1147,11 +1248,9 @@ func TestCreateInNamespace(t *testing.T) {
t: t, t: t,
name: "bar", name: "bar",
namespace: "other", namespace: "other",
expectedSet: "/prefix/version/foo/bar?namespace=other", expectedSet: "/api/version/foo/bar?namespace=other",
} }
handler := Handle(map[string]RESTStorage{ handler := handleLinker(map[string]RESTStorage{"foo": &storage}, selfLinker)
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -1160,7 +1259,7 @@ func TestCreateInNamespace(t *testing.T) {
Other: "bar", Other: "bar",
} }
data, _ := codec.Encode(simple) data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?namespace=other", bytes.NewBuffer(data)) request, err := http.NewRequest("POST", server.URL+"/api/version/foo?namespace=other", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@@ -1205,11 +1304,9 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
t: t, t: t,
name: "bar", name: "bar",
namespace: "other", namespace: "other",
expectedSet: "/prefix/version/foo/bar?namespace=other", expectedSet: "/api/version/foo/bar?namespace=other",
} }
handler := Handle(map[string]RESTStorage{ handler := handleInternal(map[string]RESTStorage{"foo": &storage}, deny.NewAlwaysDeny(), mapper, selfLinker)
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -1218,7 +1315,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
Other: "bar", Other: "bar",
} }
data, _ := codec.Encode(simple) data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?namespace=other", bytes.NewBuffer(data)) request, err := http.NewRequest("POST", server.URL+"/api/version/foo?namespace=other", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@@ -1269,11 +1366,11 @@ func TestDelayReturnsError(t *testing.T) {
return nil, apierrs.NewAlreadyExists("foo", "bar") return nil, apierrs.NewAlreadyExists("foo", "bar")
}, },
} }
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) handler := handle(map[string]RESTStorage{"foo": &storage})
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/prefix/version/foo/bar", server.URL), nil, http.StatusConflict) status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/api/version/foo/bar", server.URL), nil, http.StatusConflict)
if status.Status != api.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != api.StatusReasonAlreadyExists { if status.Status != api.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != api.StatusReasonAlreadyExists {
t.Errorf("Unexpected status %#v", status) t.Errorf("Unexpected status %#v", status)
} }
@@ -1333,15 +1430,15 @@ func TestCreateTimeout(t *testing.T) {
return obj, nil return obj, nil
}, },
} }
handler := Handle(map[string]RESTStorage{ handler := handle(map[string]RESTStorage{
"foo": &storage, "foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) })
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
simple := &Simple{Other: "foo"} simple := &Simple{Other: "foo"}
data, _ := codec.Encode(simple) data, _ := codec.Encode(simple)
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?timeout=4ms", data, apierrs.StatusServerTimeout) itemOut := expectApiStatus(t, "POST", server.URL+"/api/version/foo?timeout=4ms", data, apierrs.StatusServerTimeout)
if itemOut.Status != api.StatusFailure || itemOut.Reason != api.StatusReasonTimeout { if itemOut.Status != api.StatusFailure || itemOut.Reason != api.StatusReasonTimeout {
t.Errorf("Unexpected status %#v", itemOut) t.Errorf("Unexpected status %#v", itemOut)
} }
@@ -1367,7 +1464,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
} }
handler := CORS( handler := CORS(
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper), handle(map[string]RESTStorage{}),
allowedOriginRegexps, nil, nil, "true", allowedOriginRegexps, nil, nil, "true",
) )
server := httptest.NewServer(handler) server := httptest.NewServer(handler)

View File

@@ -280,14 +280,10 @@ func TestProxy(t *testing.T) {
expectedResourceNamespace: item.reqNamespace, expectedResourceNamespace: item.reqNamespace,
} }
namespaceHandler := Handle(map[string]RESTStorage{ namespaceHandler := handleNamespaced(map[string]RESTStorage{"foo": simpleStorage})
"foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper)
namespaceServer := httptest.NewServer(namespaceHandler) namespaceServer := httptest.NewServer(namespaceHandler)
defer namespaceServer.Close() defer namespaceServer.Close()
legacyNamespaceHandler := Handle(map[string]RESTStorage{ legacyNamespaceHandler := handle(map[string]RESTStorage{"foo": simpleStorage})
"foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper)
legacyNamespaceServer := httptest.NewServer(legacyNamespaceHandler) legacyNamespaceServer := httptest.NewServer(legacyNamespaceHandler)
defer legacyNamespaceServer.Close() defer legacyNamespaceServer.Close()
@@ -296,8 +292,8 @@ func TestProxy(t *testing.T) {
server *httptest.Server server *httptest.Server
proxyTestPattern string proxyTestPattern string
}{ }{
{namespaceServer, "/prefix/version/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path}, {namespaceServer, "/api/version/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
{legacyNamespaceServer, "/prefix/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace}, {legacyNamespaceServer, "/api/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
} }
for _, serverPattern := range serverPatterns { for _, serverPattern := range serverPatterns {
@@ -344,14 +340,12 @@ func TestProxyUpgrade(t *testing.T) {
expectedResourceNamespace: "myns", expectedResourceNamespace: "myns",
} }
namespaceHandler := Handle(map[string]RESTStorage{ namespaceHandler := handleNamespaced(map[string]RESTStorage{"foo": simpleStorage})
"foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper)
server := httptest.NewServer(namespaceHandler) server := httptest.NewServer(namespaceHandler)
defer server.Close() defer server.Close()
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/prefix/version/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/") ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
if err != nil { if err != nil {
t.Fatalf("websocket dial err: %s", err) t.Fatalf("websocket dial err: %s", err)
} }

View File

@@ -29,9 +29,7 @@ func TestRedirect(t *testing.T) {
errors: map[string]error{}, errors: map[string]error{},
expectedResourceNamespace: "default", expectedResourceNamespace: "default",
} }
handler := Handle(map[string]RESTStorage{ handler := handle(map[string]RESTStorage{"foo": simpleStorage})
"foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -54,7 +52,7 @@ func TestRedirect(t *testing.T) {
for _, item := range table { for _, item := range table {
simpleStorage.errors["resourceLocation"] = item.err simpleStorage.errors["resourceLocation"] = item.err
simpleStorage.resourceLocation = item.id simpleStorage.resourceLocation = item.id
resp, err := client.Get(server.URL + "/prefix/version/redirect/foo/" + item.id) resp, err := client.Get(server.URL + "/api/version/redirect/foo/" + item.id)
if resp == nil { if resp == nil {
t.Fatalf("Unexpected nil resp") t.Fatalf("Unexpected nil resp")
} }
@@ -82,9 +80,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
errors: map[string]error{}, errors: map[string]error{},
expectedResourceNamespace: "other", expectedResourceNamespace: "other",
} }
handler := Handle(map[string]RESTStorage{ handler := handleNamespaced(map[string]RESTStorage{"foo": simpleStorage})
"foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -107,7 +103,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
for _, item := range table { for _, item := range table {
simpleStorage.errors["resourceLocation"] = item.err simpleStorage.errors["resourceLocation"] = item.err
simpleStorage.resourceLocation = item.id simpleStorage.resourceLocation = item.id
resp, err := client.Get(server.URL + "/prefix/version/redirect/namespaces/other/foo/" + item.id) resp, err := client.Get(server.URL + "/api/version/redirect/namespaces/other/foo/" + item.id)
if resp == nil { if resp == nil {
t.Fatalf("Unexpected nil resp") t.Fatalf("Unexpected nil resp")
} }

View File

@@ -17,6 +17,7 @@ limitations under the License.
package apiserver package apiserver
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
gpath "path" gpath "path"
@@ -97,7 +98,7 @@ func parseSelectorQueryParams(query url.Values, version, apiResource string) (la
} }
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object. // ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, requestInfoResolver *APIRequestInfoResolver) restful.RouteFunction { func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, version, apiResource string) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) { return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter w := res.ResponseWriter
@@ -109,13 +110,7 @@ func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runti
ctx := ctxFn(req) ctx := ctxFn(req)
ctx = api.WithNamespace(ctx, namespace) ctx = api.WithNamespace(ctx, namespace)
requestInfo, err := requestInfoResolver.GetAPIRequestInfo(req.Request) label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), version, apiResource)
if err != nil {
errorJSON(err, codec, w)
return
}
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), requestInfo.APIVersion, requestInfo.Resource)
if err != nil { if err != nil {
errorJSON(err, codec, w) errorJSON(err, codec, w)
return return
@@ -135,7 +130,7 @@ func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runti
} }
// CreateResource returns a function that will handle a resource creation. // CreateResource returns a function that will handle a resource creation.
func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction { func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, typer runtime.ObjectTyper, resource string, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) { return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter w := res.ResponseWriter
@@ -158,6 +153,7 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec ru
obj := r.New() obj := r.New()
if err := codec.DecodeInto(body, obj); err != nil { if err := codec.DecodeInto(body, obj); err != nil {
err = transformDecodeError(typer, err, obj, body)
errorJSON(err, codec, w) errorJSON(err, codec, w)
return return
} }
@@ -190,7 +186,7 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec ru
} }
// UpdateResource returns a function that will handle a resource update // UpdateResource returns a function that will handle a resource update
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction { func UpdateResource(r RESTUpdater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, typer runtime.ObjectTyper, resource string, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) { return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter w := res.ResponseWriter
@@ -213,6 +209,7 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, namer ScopeNamer, codec ru
obj := r.New() obj := r.New()
if err := codec.DecodeInto(body, obj); err != nil { if err := codec.DecodeInto(body, obj); err != nil {
err = transformDecodeError(typer, err, obj, body)
errorJSON(err, codec, w) errorJSON(err, codec, w)
return return
} }
@@ -346,6 +343,18 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
} }
} }
// transformDecodeError adds additional information when a decode fails.
func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, body []byte) error {
_, kind, err := typer.ObjectVersionAndKind(into)
if err != nil {
return err
}
if version, dataKind, err := typer.DataVersionAndKind(body); err == nil && len(dataKind) > 0 {
return errors.NewBadRequest(fmt.Sprintf("%s in version %s cannot be handled as a %s: %v", dataKind, version, kind, baseErr))
}
return errors.NewBadRequest(fmt.Sprintf("the object provided is unrecognized (must be of type %s): %v", kind, baseErr))
}
// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request // setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request
// plus the path and query generated by the provided linkFunc // plus the path and query generated by the provided linkFunc
func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error { func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {

View File

@@ -49,9 +49,7 @@ var watchTestTable = []struct {
func TestWatchWebsocket(t *testing.T) { func TestWatchWebsocket(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
_ = 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, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -103,9 +101,7 @@ func TestWatchWebsocket(t *testing.T) {
func TestWatchHTTP(t *testing.T) { 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, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@@ -170,9 +166,7 @@ func TestWatchParamParsing(t *testing.T) {
return label, value, nil return label, value, nil
}) })
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := handle(map[string]RESTStorage{"foo": simpleStorage})
"foo": simpleStorage,
}, codec, "/api", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@@ -242,9 +236,7 @@ func TestWatchParamParsing(t *testing.T) {
func TestWatchProtocolSelection(t *testing.T) { 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, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
defer server.CloseClientConnections() defer server.CloseClientConnections()

View File

@@ -52,3 +52,8 @@ func (c *FakePods) Update(pod *api.Pod) (*api.Pod, error) {
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "update-pod", Value: pod.Name}) c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "update-pod", Value: pod.Name})
return &api.Pod{}, nil return &api.Pod{}, nil
} }
func (c *FakePods) Bind(bind *api.Binding) error {
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "bind-pod", Value: bind.Name})
return nil
}

View File

@@ -36,6 +36,8 @@ type PodInterface interface {
Delete(name string) error Delete(name string) error
Create(pod *api.Pod) (*api.Pod, error) Create(pod *api.Pod) (*api.Pod, error)
Update(pod *api.Pod) (*api.Pod, error) Update(pod *api.Pod) (*api.Pod, error)
Bind(binding *api.Binding) error
} }
// pods implements PodsNamespacer interface // pods implements PodsNamespacer interface
@@ -92,3 +94,8 @@ func (c *pods) Update(pod *api.Pod) (result *api.Pod, err error) {
err = c.r.Put().Namespace(c.ns).Resource("pods").Name(pod.Name).Body(pod).Do().Into(result) err = c.r.Put().Namespace(c.ns).Resource("pods").Name(pod.Name).Body(pod).Do().Into(result)
return return
} }
// Bind applies the provided binding to the named pod in the current namespace (binding.Namespace is ignored).
func (c *pods) Bind(binding *api.Binding) error {
return c.r.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error()
}

View File

@@ -105,6 +105,7 @@ type Request struct {
namespaceSet bool namespaceSet bool
resource string resource string
resourceName string resourceName string
subresource string
selector labels.Selector selector labels.Selector
timeout time.Duration timeout time.Duration
@@ -166,6 +167,21 @@ func (r *Request) Resource(resource string) *Request {
return r return r
} }
// SubResource sets a sub-resource path which can be multiple segments segment after the resource
// name but before the suffix.
func (r *Request) SubResource(subresources ...string) *Request {
if r.err != nil {
return r
}
subresource := path.Join(subresources...)
if len(r.subresource) != 0 {
r.err = fmt.Errorf("subresource already set to %q, cannot change to %q", r.resource, subresource)
return r
}
r.subresource = subresource
return r
}
// Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>) // Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Name(resourceName string) *Request { func (r *Request) Name(resourceName string) *Request {
if r.err != nil { if r.err != nil {
@@ -360,8 +376,8 @@ func (r *Request) finalURL() string {
p = path.Join(p, resource) p = path.Join(p, resource)
} }
// Join trims trailing slashes, so preserve r.path's trailing slash for backwards compat if nothing was changed // Join trims trailing slashes, so preserve r.path's trailing slash for backwards compat if nothing was changed
if len(r.resourceName) != 0 || len(r.subpath) != 0 { if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 {
p = path.Join(p, r.resourceName, r.subpath) p = path.Join(p, r.resourceName, r.subresource, r.subpath)
} }
finalURL := *r.baseURL finalURL := *r.baseURL

View File

@@ -129,6 +129,16 @@ func TestRequestOrdersNamespaceInPath(t *testing.T) {
} }
} }
func TestRequestOrdersSubResource(t *testing.T) {
r := (&Request{
baseURL: &url.URL{},
path: "/test/",
}).Name("bar").Resource("baz").Namespace("foo").Suffix("test").SubResource("a", "b")
if s := r.finalURL(); s != "/test/namespaces/foo/baz/bar/a/b/test" {
t.Errorf("namespace should be in order in path: %s", s)
}
}
func TestRequestSetTwiceError(t *testing.T) { func TestRequestSetTwiceError(t *testing.T) {
if (&Request{}).Name("bar").Name("baz").err == nil { if (&Request{}).Name("bar").Name("baz").err == nil {
t.Errorf("setting name twice should result in error") t.Errorf("setting name twice should result in error")
@@ -139,6 +149,9 @@ func TestRequestSetTwiceError(t *testing.T) {
if (&Request{}).Resource("bar").Resource("baz").err == nil { if (&Request{}).Resource("bar").Resource("baz").err == nil {
t.Errorf("setting resource twice should result in error") t.Errorf("setting resource twice should result in error")
} }
if (&Request{}).SubResource("bar").SubResource("baz").err == nil {
t.Errorf("setting subresource twice should result in error")
}
} }
func TestRequestParseSelectorParam(t *testing.T) { func TestRequestParseSelectorParam(t *testing.T) {

View File

@@ -30,7 +30,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
@@ -55,7 +54,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/ui" "github.com/GoogleCloudPlatform/kubernetes/pkg/ui"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@@ -406,9 +404,10 @@ func (m *Master) init(c *Config) {
// TODO: Factor out the core API registration // TODO: Factor out the core API registration
m.storage = map[string]apiserver.RESTStorage{ m.storage = map[string]apiserver.RESTStorage{
"pods": podStorage, "pods": podStorage,
"pods/status": podStatusStorage, "pods/status": podStatusStorage,
"bindings": bindingStorage, "pods/binding": bindingStorage,
"bindings": bindingStorage,
"replicationControllers": controller.NewREST(registry, podRegistry), "replicationControllers": controller.NewREST(registry, podRegistry),
"services": service.NewREST(m.serviceRegistry, c.Cloud, m.nodeRegistry, m.portalNet, c.ClusterName), "services": service.NewREST(m.serviceRegistry, c.Cloud, m.nodeRegistry, m.portalNet, c.ClusterName),
@@ -425,14 +424,14 @@ func (m *Master) init(c *Config) {
} }
apiVersions := []string{"v1beta1", "v1beta2"} apiVersions := []string{"v1beta1", "v1beta2"}
if err := apiserver.NewAPIGroupVersion(m.api_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1"); err != nil { if err := m.api_v1beta1().InstallREST(m.handlerContainer); err != nil {
glog.Fatalf("Unable to setup API v1beta1: %v", err) glog.Fatalf("Unable to setup API v1beta1: %v", err)
} }
if err := apiserver.NewAPIGroupVersion(m.api_v1beta2()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta2"); err != nil { if err := m.api_v1beta2().InstallREST(m.handlerContainer); err != nil {
glog.Fatalf("Unable to setup API v1beta2: %v", err) glog.Fatalf("Unable to setup API v1beta2: %v", err)
} }
if c.EnableV1Beta3 { if c.EnableV1Beta3 {
if err := apiserver.NewAPIGroupVersion(m.api_v1beta3()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta3"); err != nil { if err := m.api_v1beta3().InstallREST(m.handlerContainer); err != nil {
glog.Fatalf("Unable to setup API v1beta3: %v", err) glog.Fatalf("Unable to setup API v1beta3: %v", err)
} }
apiVersions = []string{"v1beta1", "v1beta2", "v1beta3"} apiVersions = []string{"v1beta1", "v1beta2", "v1beta3"}
@@ -569,26 +568,49 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
return serversToValidate return serversToValidate
} }
func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
return &apiserver.APIGroupVersion{
Root: m.apiPrefix,
Mapper: latest.RESTMapper,
Creater: api.Scheme,
Typer: api.Scheme,
Linker: latest.SelfLinker,
Admit: m.admissionControl,
Context: m.requestContextMapper,
}
}
// api_v1beta1 returns the resources and codec for API version v1beta1. // api_v1beta1 returns the resources and codec for API version v1beta1.
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) { func (m *Master) api_v1beta1() *apiserver.APIGroupVersion {
storage := make(map[string]apiserver.RESTStorage) storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage { for k, v := range m.storage {
storage[k] = v storage[k] = v
} }
return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper version := m.defaultAPIGroupVersion()
version.Storage = storage
version.Version = "v1beta1"
version.Codec = v1beta1.Codec
return version
} }
// api_v1beta2 returns the resources and codec for API version v1beta2. // api_v1beta2 returns the resources and codec for API version v1beta2.
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) { func (m *Master) api_v1beta2() *apiserver.APIGroupVersion {
storage := make(map[string]apiserver.RESTStorage) storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage { for k, v := range m.storage {
storage[k] = v storage[k] = v
} }
return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper version := m.defaultAPIGroupVersion()
version.Storage = storage
version.Version = "v1beta2"
version.Codec = v1beta2.Codec
return version
} }
// api_v1beta3 returns the resources and codec for API version v1beta3. // api_v1beta3 returns the resources and codec for API version v1beta3.
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) { func (m *Master) api_v1beta3() *apiserver.APIGroupVersion {
storage := make(map[string]apiserver.RESTStorage) storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage { for k, v := range m.storage {
if k == "minions" { if k == "minions" {
@@ -596,5 +618,9 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec,
} }
storage[strings.ToLower(k)] = v storage[strings.ToLower(k)] = v
} }
return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper version := m.defaultAPIGroupVersion()
version.Storage = storage
version.Version = "v1beta3"
version.Codec = v1beta3.Codec
return version
} }

View File

@@ -1,21 +0,0 @@
/*
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 binding contains the middle layer logic for bindings.
// Bindings are objects containing instructions for how a pod ought to
// be bound to a host. This allows a registry object which supports this
// action (ApplyBinding) to be served through an apiserver.
package binding

View File

@@ -1,30 +0,0 @@
/*
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 binding
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
// MockRegistry can be used for testing.
type MockRegistry struct {
OnApplyBinding func(binding *api.Binding) error
}
func (mr MockRegistry) ApplyBinding(ctx api.Context, binding *api.Binding) error {
return mr.OnApplyBinding(binding)
}

View File

@@ -1,28 +0,0 @@
/*
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 binding
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
// Registry contains the functions needed to support a BindingStorage.
type Registry interface {
// ApplyBinding should apply the binding. That is, it should actually
// assign or place pod binding.PodID on machine binding.Host.
ApplyBinding(ctx api.Context, binding *api.Binding) error
}

View File

@@ -1,56 +0,0 @@
/*
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 binding
import (
"fmt"
"net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
// REST implements the RESTStorage interface for bindings. When bindings are written, it
// changes the location of the affected pods. This information is eventually reflected
// in the pod's CurrentState.Host field.
type REST struct {
registry Registry
}
// NewREST creates a new REST backed by the given bindingRegistry.
func NewREST(bindingRegistry Registry) *REST {
return &REST{
registry: bindingRegistry,
}
}
// New returns a new binding object fit for having data unmarshalled into it.
func (*REST) New() runtime.Object {
return &api.Binding{}
}
// Create attempts to make the assignment indicated by the binding it recieves.
func (b *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
binding, ok := obj.(*api.Binding)
if !ok {
return nil, fmt.Errorf("incorrect type: %#v", obj)
}
if err := b.registry.ApplyBinding(ctx, binding); err != nil {
return nil, err
}
return &api.Status{Status: api.StatusSuccess, Code: http.StatusCreated}, nil
}

View File

@@ -1,91 +0,0 @@
/*
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 binding
import (
"errors"
"net/http"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
)
func TestNewREST(t *testing.T) {
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error { return nil },
}
b := NewREST(mockRegistry)
binding := &api.Binding{
PodID: "foo",
Host: "bar",
}
body, err := latest.Codec.Encode(binding)
if err != nil {
t.Fatalf("Unexpected encode error %v", err)
}
obj := b.New()
err = latest.Codec.DecodeInto(body, obj)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := binding, obj; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, but got %#v", e, a)
}
}
func TestRESTPost(t *testing.T) {
table := []struct {
b *api.Binding
err error
}{
{b: &api.Binding{PodID: "foo", Host: "bar"}, err: errors.New("no host bar")},
{b: &api.Binding{PodID: "baz", Host: "qux"}, err: nil},
{b: &api.Binding{PodID: "dvorak", Host: "qwerty"}, err: nil},
}
for i, item := range table {
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error {
if !reflect.DeepEqual(item.b, b) {
t.Errorf("%v: expected %#v, but got %#v", i, item, b)
}
return item.err
},
}
ctx := api.NewContext()
b := NewREST(mockRegistry)
result, err := b.Create(ctx, item.b)
if err != nil && item.err == nil {
t.Errorf("Unexpected error %v", err)
continue
}
if err == nil && item.err != nil {
t.Errorf("Unexpected error %v", err)
continue
}
var expect interface{}
if item.err == nil {
expect = &api.Status{Status: api.StatusSuccess, Code: http.StatusCreated}
}
if e, a := expect, result; !reflect.DeepEqual(e, a) {
t.Errorf("%v: expected %#v, got %#v", i, e, a)
}
}
}

View File

@@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd" etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/constraint" "github.com/GoogleCloudPlatform/kubernetes/pkg/constraint"
@@ -146,7 +147,14 @@ func (r *BindingREST) New() runtime.Object {
// Create ensures a pod is bound to a specific host. // Create ensures a pod is bound to a specific host.
func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) { func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) {
binding := obj.(*api.Binding) binding := obj.(*api.Binding)
err = r.assignPod(ctx, binding.PodID, binding.Host) // TODO: move me to a binding strategy
if len(binding.Target.Kind) != 0 && (binding.Target.Kind != "Node" && binding.Target.Kind != "Minion") {
return nil, errors.NewInvalid("binding", binding.Name, errors.ValidationErrorList{errors.NewFieldInvalid("to.kind", binding.Target.Kind, "must be empty, 'Node', or 'Minion'")})
}
if len(binding.Target.Name) == 0 {
return nil, errors.NewInvalid("binding", binding.Name, errors.ValidationErrorList{errors.NewFieldRequired("to.name", binding.Target.Name)})
}
err = r.assignPod(ctx, binding.Name, binding.Target.Name)
err = etcderr.InterpretCreateError(err, "binding", "") err = etcderr.InterpretCreateError(err, "binding", "")
out = &api.Status{Status: api.StatusSuccess} out = &api.Status{Status: api.StatusSuccess}
return return

View File

@@ -817,7 +817,10 @@ func TestEtcdCreate(t *testing.T) {
} }
// Suddenly, a wild scheduler appears: // Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine", ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}}) _, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -865,7 +868,10 @@ func TestEtcdCreateBindingNoPod(t *testing.T) {
// - Create (apiserver) // - Create (apiserver)
// - Schedule (scheduler) // - Schedule (scheduler)
// - Delete (apiserver) // - Delete (apiserver)
_, err := bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine", ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}}) _, err := bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err == nil { if err == nil {
t.Fatalf("Expected not-found-error but got nothing") t.Fatalf("Expected not-found-error but got nothing")
} }
@@ -935,7 +941,10 @@ func TestEtcdCreateWithContainersError(t *testing.T) {
} }
// Suddenly, a wild scheduler appears: // Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine"}) _, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if !errors.IsAlreadyExists(err) { if !errors.IsAlreadyExists(err) {
t.Fatalf("Unexpected error returned: %#v", err) t.Fatalf("Unexpected error returned: %#v", err)
} }
@@ -973,7 +982,10 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) {
} }
// Suddenly, a wild scheduler appears: // Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine"}) _, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -1025,7 +1037,10 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
} }
// Suddenly, a wild scheduler appears: // Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine"}) _, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -1055,6 +1070,70 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
} }
} }
func TestEtcdCreateBinding(t *testing.T) {
registry, bindingRegistry, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext()
fakeClient.TestIndex = true
testCases := map[string]struct {
binding api.Binding
errOK func(error) bool
}{
"noName": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{},
},
errOK: func(err error) bool { return errors.IsInvalid(err) },
},
"badKind": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine", Kind: "unknown"},
},
errOK: func(err error) bool { return errors.IsInvalid(err) },
},
"emptyKind": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
},
errOK: func(err error) bool { return err == nil },
},
"kindNode": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine", Kind: "Node"},
},
errOK: func(err error) bool { return err == nil },
},
"kindMinion": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine", Kind: "Minion"},
},
errOK: func(err error) bool { return err == nil },
},
}
for k, test := range testCases {
key, _ := registry.store.KeyFunc(ctx, "foo")
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
fakeClient.Set("/registry/nodes/machine/boundpods", runtime.EncodeOrDie(latest.Codec, &api.BoundPods{}), 0)
if _, err := registry.Create(ctx, validNewPod()); err != nil {
t.Fatalf("%s: unexpected error: %v", k, err)
}
fakeClient.Set("/registry/nodes/machine/boundpods", runtime.EncodeOrDie(latest.Codec, &api.BoundPods{}), 0)
if _, err := bindingRegistry.Create(ctx, &test.binding); !test.errOK(err) {
t.Errorf("%s: unexpected error: %v", k, err)
}
}
}
func TestEtcdUpdateNotFound(t *testing.T) { func TestEtcdUpdateNotFound(t *testing.T) {
registry, _, _, fakeClient, _ := newStorage(t) registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()

View File

@@ -45,6 +45,11 @@ type ObjectTyper interface {
ObjectVersionAndKind(Object) (version, kind string, err error) ObjectVersionAndKind(Object) (version, kind string, err error)
} }
// ObjectCreater contains methods for instantiating an object by kind and version.
type ObjectCreater interface {
New(version, kind string) (out Object, err error)
}
// ResourceVersioner provides methods for setting and retrieving // ResourceVersioner provides methods for setting and retrieving
// the resource version from an API object. // the resource version from an API object.
type ResourceVersioner interface { type ResourceVersioner interface {

View File

@@ -287,9 +287,11 @@ type binder struct {
// Bind just does a POST binding RPC. // Bind just does a POST binding RPC.
func (b *binder) Bind(binding *api.Binding) error { func (b *binder) Bind(binding *api.Binding) error {
glog.V(2).Infof("Attempting to bind %v to %v", binding.PodID, binding.Host) glog.V(2).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name)
ctx := api.WithNamespace(api.NewContext(), binding.Namespace) ctx := api.WithNamespace(api.NewContext(), binding.Namespace)
return b.Post().Namespace(api.NamespaceValue(ctx)).Resource("bindings").Body(binding).Do().Error() return b.Post().Namespace(api.NamespaceValue(ctx)).Resource("bindings").Body(binding).Do().Error()
// TODO: use Pods interface for binding once clusters are upgraded
// return b.Pods(binding.Namespace).Bind(binding)
} }
type clock interface { type clock interface {

View File

@@ -366,9 +366,11 @@ func TestBind(t *testing.T) {
{binding: &api.Binding{ {binding: &api.Binding{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
Name: "foo",
},
Target: api.ObjectReference{
Name: "foohost.kubernetes.mydomain.com",
}, },
PodID: "foo",
Host: "foohost.kubernetes.mydomain.com",
}}, }},
} }

View File

@@ -80,9 +80,11 @@ func (s *Scheduler) scheduleOne() {
return return
} }
b := &api.Binding{ b := &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: pod.Namespace}, ObjectMeta: api.ObjectMeta{Namespace: pod.Namespace, Name: pod.Name},
PodID: pod.Name, Target: api.ObjectReference{
Host: dest, Kind: "Node",
Name: dest,
},
} }
if err := s.config.Binder.Bind(b); err != nil { if err := s.config.Binder.Bind(b); err != nil {
glog.V(1).Infof("Failed to bind pod: %v", err) glog.V(1).Infof("Failed to bind pod: %v", err)

View File

@@ -25,6 +25,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
"github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler" "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
type fakeBinder struct { type fakeBinder struct {
@@ -63,7 +64,7 @@ func TestScheduler(t *testing.T) {
{ {
sendPod: podWithID("foo"), sendPod: podWithID("foo"),
algo: mockScheduler{"machine1", nil}, algo: mockScheduler{"machine1", nil},
expectBind: &api.Binding{PodID: "foo", Host: "machine1"}, expectBind: &api.Binding{ObjectMeta: api.ObjectMeta{Name: "foo"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}},
eventReason: "scheduled", eventReason: "scheduled",
}, { }, {
sendPod: podWithID("foo"), sendPod: podWithID("foo"),
@@ -74,7 +75,7 @@ func TestScheduler(t *testing.T) {
}, { }, {
sendPod: podWithID("foo"), sendPod: podWithID("foo"),
algo: mockScheduler{"machine1", nil}, algo: mockScheduler{"machine1", nil},
expectBind: &api.Binding{PodID: "foo", Host: "machine1"}, expectBind: &api.Binding{ObjectMeta: api.ObjectMeta{Name: "foo"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}},
injectBindError: errB, injectBindError: errB,
expectError: errB, expectError: errB,
expectErrorPod: podWithID("foo"), expectErrorPod: podWithID("foo"),
@@ -120,7 +121,7 @@ func TestScheduler(t *testing.T) {
t.Errorf("%v: error: wanted %v, got %v", i, e, a) t.Errorf("%v: error: wanted %v, got %v", i, e, a)
} }
if e, a := item.expectBind, gotBinding; !reflect.DeepEqual(e, a) { if e, a := item.expectBind, gotBinding; !reflect.DeepEqual(e, a) {
t.Errorf("%v: error: wanted %v, got %v", i, e, a) t.Errorf("%v: error: %s", i, util.ObjectDiff(e, a))
} }
<-called <-called
events.Stop() events.Stop()