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{}
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) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
j.TemplateRef = nil // this is required for round trip

View File

@@ -918,13 +918,14 @@ type NamespaceList struct {
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 {
TypeMeta `json:",inline"`
// ObjectMeta describes the object that is being bound.
ObjectMeta `json:"metadata,omitempty"`
PodID string `json:"podID"`
Host string `json:"host"`
// Target is the object to bind to.
Target ObjectReference `json:"target"`
}
// Status is a return value for calls that don't return other objects.

View File

@@ -1324,6 +1324,24 @@ func init() {
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 one of the conversion functions is malformed, detect it immediately.

View File

@@ -1240,6 +1240,24 @@ func init() {
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 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"`
}
// Binding is written by a scheduler to cause a pod to be bound to a node. Name is not
// required for Bindings.
// Binding ties one object to another - for example, a pod is bound to a node by a scheduler.
type Binding struct {
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"`
// PodID is a Pod name to be bound to a node.
PodID string `json:"podID" description:"name of the pod to be bound to a node"`
// Host is the name of a node to bind to.
Host string `json:"host" description:"name of the node to bind to"`
// Target is the object to bind to.
Target ObjectReference `json:"target" description:"an object to bind to"`
}
// Status is a return value for calls that don't return other objects.

View File

@@ -26,7 +26,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/emicklei/go-restful"
@@ -34,8 +33,8 @@ import (
type APIInstaller struct {
group *APIGroupVersion
info *APIRequestInfoResolver
prefix string // Path prefix where API resources are to be registered.
version string // The API version being installed.
}
// 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.
watchHandler := (&WatchHandler{
storage: a.group.storage,
codec: a.group.codec,
linker: a.group.linker,
info: a.group.info,
storage: a.group.Storage,
codec: a.group.Codec,
linker: a.group.Linker,
info: a.info,
})
redirectHandler := (&RedirectHandler{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.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.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 {
errors = append(errors, err)
}
@@ -77,18 +76,17 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
func (a *APIInstaller) newWebService() *restful.WebService {
ws := new(restful.WebService)
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
ws.Consumes("*/*")
ws.Produces(restful.MIME_JSON)
ws.ApiVersion(a.version)
ws.ApiVersion(a.group.Version)
return ws
}
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
context := a.group.context
admit := a.group.Admit
context := a.group.Context
var resource, subresource string
switch parts := strings.Split(path, "/"); len(parts) {
@@ -97,19 +95,16 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
case 1:
resource = parts[0]
default:
// TODO: support deeper paths
return fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)")
}
object := storage.New()
// TODO: add scheme to APIInstaller rather than using api.Scheme
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
_, kind, err := a.group.Typer.ObjectVersionAndKind(object)
if err != nil {
return err
}
versionedPtr, err := api.Scheme.New(a.version, kind)
if conversion.IsNotRegisteredError(err) {
return nil
}
versionedPtr, err := a.group.Creater.New(a.group.Version, kind)
if err != nil {
return err
}
@@ -118,15 +113,15 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
var versionedList interface{}
if lister, ok := storage.(RESTLister); ok {
list := lister.NewList()
_, listKind, err := api.Scheme.ObjectVersionAndKind(list)
versionedListPtr, err := api.Scheme.New(a.version, listKind)
_, listKind, err := a.group.Typer.ObjectVersionAndKind(list)
versionedListPtr, err := a.group.Creater.New(a.group.Version, listKind)
if err != nil {
return err
}
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 {
return err
}
@@ -156,25 +151,27 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
// Get the list of actions for the given scope.
if scope.Name() != meta.RESTScopeNameNamespace {
itemPath := resource + "/{name}"
resourcePath := resource
itemPath := resourcePath + "/{name}"
if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource
resourcePath = itemPath
}
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).
actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister)
actions = appendIf(actions, action{"POST", resource, params, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList)
actions = appendIf(actions, action{"LIST", resourcePath, params, namer}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, params, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, params, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, isWatcher)
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, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
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, nameParams, namer}, isRedirector)
} else {
// v1beta3 format with namespace in path
@@ -184,29 +181,31 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + resource
namespaceParams := []*restful.Parameter{namespaceParam}
resourcePath := namespacedPath
itemPath := namespacedPath + "/{name}"
if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource
resourcePath = itemPath
}
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{"POST", namespacedPath, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"LIST", resourcePath, namespaceParams, namer}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, isWatcher)
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, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
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, nameParams, namer}, isRedirector)
// 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{"WATCHLIST", "/watch/" + resource, params, namer}, allowWatchList)
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer}, allowWatchList)
} else {
// 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")
namespaceParams := []*restful.Parameter{namespaceParam}
itemPath := resource + "/{name}"
basePath := resource
resourcePath := basePath
itemPath := resourcePath + "/{name}"
if len(subresource) > 0 {
itemPath = itemPath + "/" + subresource
resourcePath = itemPath
}
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{"POST", resource, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + resource, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"LIST", resourcePath, namespaceParams, namer}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, namespaceParams, namer}, isCreater)
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, isWatcher)
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, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
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, nameParams, namer}, isRedirector)
}
}
@@ -256,7 +258,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
m := monitorFilter(action.Verb, resource)
switch action.Verb {
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).
Doc("read the specified " + kind).
Operation("read" + kind).
@@ -264,7 +266,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params)
ws.Route(route)
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).
Doc("list objects of kind " + kind).
Operation("list" + kind).
@@ -272,7 +274,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params)
ws.Route(route)
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).
Doc("replace the specified " + kind).
Operation("replace" + kind).
@@ -280,7 +282,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params)
ws.Route(route)
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).
Doc("create a " + kind).
Operation("create" + kind).
@@ -288,7 +290,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
addParams(route, action.Params)
ws.Route(route)
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).
Doc("delete a " + kind).
Operation("delete" + kind)

View File

@@ -29,7 +29,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"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/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@@ -91,72 +90,38 @@ type Mux interface {
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
// It handles URLs of the form:
// /${storage_key}[/${object_name}]
// Where 'storage_key' points to a RESTStorage object stored in storage.
type APIGroupVersion struct {
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
}
Storage map[string]RESTStorage
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their
// associated operations. The provided codec controls serialization and deserialization.
// This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server.
// TODO: add multitype codec serialization
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) *APIGroupVersion {
return &APIGroupVersion{
storage: storage,
codec: codec,
prefix: prefix,
linker: linker,
admit: admissionControl,
context: contextMapper,
mapper: mapper,
info: &APIRequestInfoResolver{util.NewStringSet(strings.TrimPrefix(root, "/")), latest.RESTMapper},
}
Root string
Version string
Mapper meta.RESTMapper
Codec runtime.Codec
Typer runtime.ObjectTyper
Creater runtime.ObjectCreater
Linker runtime.SelfLinker
Admit admission.Interface
Context api.RequestContextMapper
}
// 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
// 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 {
prefix := path.Join(root, version)
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
info := &APIRequestInfoResolver{util.NewStringSet(strings.TrimPrefix(g.Root, "/")), g.Mapper}
prefix := path.Join(g.Root, g.Version)
installer := &APIInstaller{
group: g,
info: info,
prefix: prefix,
version: version,
}
ws, registrationErrors := installer.Install()
container.Add(ws)

View File

@@ -41,6 +41,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
"github.com/emicklei/go-restful"
)
func convert(obj runtime.Object) (runtime.Object, error) {
@@ -115,6 +117,59 @@ func init() {
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 {
api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata"`
@@ -285,21 +340,21 @@ func TestNotFound(t *testing.T) {
Status int
}
cases := map[string]T{
"PATCH method": {"PATCH", "/prefix/version/foo", http.StatusMethodNotAllowed},
"GET long prefix": {"GET", "/prefix/", http.StatusNotFound},
"GET missing storage": {"GET", "/prefix/version/blah", http.StatusNotFound},
"GET with extra segment": {"GET", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"POST with extra segment": {"POST", "/prefix/version/foo/bar", http.StatusMethodNotAllowed},
"DELETE without extra segment": {"DELETE", "/prefix/version/foo", http.StatusMethodNotAllowed},
"DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"PUT without extra segment": {"PUT", "/prefix/version/foo", http.StatusMethodNotAllowed},
"PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"watch missing storage": {"GET", "/prefix/version/watch/", http.StatusNotFound},
"watch with bad method": {"POST", "/prefix/version/watch/foo/bar", http.StatusMethodNotAllowed},
"PATCH method": {"PATCH", "/api/version/foo", http.StatusMethodNotAllowed},
"GET long prefix": {"GET", "/api/", http.StatusNotFound},
"GET missing storage": {"GET", "/api/version/blah", http.StatusNotFound},
"GET with extra segment": {"GET", "/api/version/foo/bar/baz", http.StatusNotFound},
"POST with extra segment": {"POST", "/api/version/foo/bar", http.StatusMethodNotAllowed},
"DELETE without extra segment": {"DELETE", "/api/version/foo", http.StatusMethodNotAllowed},
"DELETE with extra segment": {"DELETE", "/api/version/foo/bar/baz", http.StatusNotFound},
"PUT without extra segment": {"PUT", "/api/version/foo", http.StatusMethodNotAllowed},
"PUT with extra segment": {"PUT", "/api/version/foo/bar/baz", http.StatusNotFound},
"watch missing storage": {"GET", "/api/version/watch/", http.StatusNotFound},
"watch with bad method": {"POST", "/api/version/watch/foo/bar", http.StatusMethodNotAllowed},
}
handler := Handle(map[string]RESTStorage{
handler := handle(map[string]RESTStorage{
"foo": &SimpleRESTStorage{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -339,19 +394,19 @@ func TestUnimplementedRESTStorage(t *testing.T) {
ErrCode int
}
cases := map[string]T{
"GET object": {"GET", "/prefix/version/foo/bar", http.StatusNotFound},
"GET list": {"GET", "/prefix/version/foo", http.StatusNotFound},
"POST list": {"POST", "/prefix/version/foo", http.StatusNotFound},
"PUT object": {"PUT", "/prefix/version/foo/bar", http.StatusNotFound},
"DELETE object": {"DELETE", "/prefix/version/foo/bar", http.StatusNotFound},
"watch list": {"GET", "/prefix/version/watch/foo", http.StatusNotFound},
"watch object": {"GET", "/prefix/version/watch/foo/bar", http.StatusNotFound},
"proxy object": {"GET", "/prefix/version/proxy/foo/bar", http.StatusNotFound},
"redirect object": {"GET", "/prefix/version/redirect/foo/bar", http.StatusNotFound},
"GET object": {"GET", "/api/version/foo/bar", http.StatusNotFound},
"GET list": {"GET", "/api/version/foo", http.StatusNotFound},
"POST list": {"POST", "/api/version/foo", http.StatusNotFound},
"PUT object": {"PUT", "/api/version/foo/bar", http.StatusNotFound},
"DELETE object": {"DELETE", "/api/version/foo/bar", http.StatusNotFound},
"watch list": {"GET", "/api/version/watch/foo", http.StatusNotFound},
"watch object": {"GET", "/api/version/watch/foo/bar", http.StatusNotFound},
"proxy object": {"GET", "/api/version/proxy/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{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -376,7 +431,7 @@ func TestUnimplementedRESTStorage(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)
defer server.Close()
client := http.Client{}
@@ -409,14 +464,14 @@ func TestList(t *testing.T) {
selfLink string
legacy bool
}{
{"/prefix/version/simple", "", "/prefix/version/simple?namespace=", true},
{"/prefix/version/simple?namespace=other", "other", "/prefix/version/simple?namespace=other", true},
{"/api/version/simple", "", "/api/version/simple?namespace=", true},
{"/api/version/simple?namespace=other", "other", "/api/version/simple?namespace=other", true},
// list items across all namespaces
{"/prefix/version/simple?namespace=", "", "/prefix/version/simple?namespace=", true},
{"/prefix/version/namespaces/default/simple", "default", "/prefix/version/namespaces/default/simple", false},
{"/prefix/version/namespaces/other/simple", "other", "/prefix/version/namespaces/other/simple", false},
{"/api/version/simple?namespace=", "", "/api/version/simple?namespace=", true},
{"/api/version/namespaces/default/simple", "default", "/api/version/namespaces/default/simple", false},
{"/api/version/namespaces/other/simple", "other", "/api/version/namespaces/other/simple", false},
// list items across all namespaces
{"/prefix/version/simple", "", "/prefix/version/simple", false},
{"/api/version/simple", "", "/api/version/simple", false},
}
for i, testCase := range testCases {
storage := map[string]RESTStorage{}
@@ -429,9 +484,9 @@ func TestList(t *testing.T) {
}
var handler http.Handler
if testCase.legacy {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler = handleLinker(storage, selfLinker)
} else {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper)
handler = handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
}
server := httptest.NewServer(handler)
defer server.Close()
@@ -462,11 +517,11 @@ func TestErrorList(t *testing.T) {
errors: map[string]error{"list": fmt.Errorf("test Error")},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
resp, err := http.Get(server.URL + "/api/version/simple")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -487,11 +542,11 @@ func TestNonEmptyList(t *testing.T) {
},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
resp, err := http.Get(server.URL + "/api/version/simple")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -515,10 +570,10 @@ func TestNonEmptyList(t *testing.T) {
if listOut.Items[0].Other != simpleStorage.list[0].Other {
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)
}
expectedSelfLink := "/prefix/version/simple/something?namespace=other"
expectedSelfLink := "/api/version/simple/something?namespace=other"
if 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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
resp, err := http.Get(server.URL + "/api/version/simple")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -562,7 +617,7 @@ func TestSelfLinkSkipsEmptyName(t *testing.T) {
if listOut.Items[0].Other != simpleStorage.list[0].Other {
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)
}
expectedSelfLink := ""
@@ -580,16 +635,16 @@ func TestGet(t *testing.T) {
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/id?namespace=default",
expectedSet: "/api/version/simple/id?namespace=default",
name: "id",
namespace: "default",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
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 {
t.Fatalf("unexpected error: %v", err)
}
@@ -619,16 +674,16 @@ func TestGetAlternateSelfLink(t *testing.T) {
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/id?namespace=test",
expectedSet: "/api/version/simple/id?namespace=test",
name: "id",
namespace: "test",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper)
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
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 {
t.Fatalf("unexpected error: %v", err)
}
@@ -657,16 +712,16 @@ func TestGetNamespaceSelfLink(t *testing.T) {
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/namespaces/foo/simple/id",
expectedSet: "/api/version/namespaces/foo/simple/id",
name: "id",
namespace: "foo",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper)
handler := handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
server := httptest.NewServer(handler)
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 {
t.Fatalf("unexpected error: %v", err)
}
@@ -691,11 +746,11 @@ func TestGetMissing(t *testing.T) {
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
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 {
t.Errorf("unexpected error: %v", err)
}
@@ -710,12 +765,12 @@ func TestDelete(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
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)
if err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -733,12 +788,12 @@ func TestDeleteInvokesAdmissionControl(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
handler := handleDeny(storage)
server := httptest.NewServer(handler)
defer server.Close()
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -755,12 +810,12 @@ func TestDeleteMissing(t *testing.T) {
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -778,11 +833,11 @@ func TestUpdate(t *testing.T) {
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/" + ID + "?namespace=default",
expectedSet: "/api/version/simple/" + ID + "?namespace=default",
name: ID,
namespace: api.NamespaceDefault,
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -800,7 +855,7 @@ func TestUpdate(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -819,7 +874,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
handler := handleDeny(storage)
server := httptest.NewServer(handler)
defer server.Close()
@@ -837,7 +892,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -852,7 +907,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
handler := handleDeny(storage)
server := httptest.NewServer(handler)
defer server.Close()
@@ -866,7 +921,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -881,7 +936,7 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
@@ -898,7 +953,7 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -918,7 +973,7 @@ func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
t: t,
err: fmt.Errorf("test error"),
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
@@ -936,7 +991,7 @@ func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -955,7 +1010,7 @@ func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
@@ -973,7 +1028,7 @@ func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -990,7 +1045,7 @@ func TestUpdateMissing(t *testing.T) {
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
@@ -1007,7 +1062,7 @@ func TestUpdateMissing(t *testing.T) {
}
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)
if err != nil {
t.Errorf("unexpected error: %v", err)
@@ -1018,20 +1073,20 @@ func TestUpdateMissing(t *testing.T) {
}
func TestCreateNotFound(t *testing.T) {
handler := Handle(map[string]RESTStorage{
handler := handle(map[string]RESTStorage{
"simple": &SimpleRESTStorage{
// storage.Create can fail with not found error in theory.
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
simple := &Simple{Other: "foo"}
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 {
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) {
if d := parseTimeout(""); d != 30*time.Second {
t.Errorf("blank timeout produces %v", d)
@@ -1089,11 +1192,9 @@ func TestCreate(t *testing.T) {
t: t,
name: "bar",
namespace: "default",
expectedSet: "/prefix/version/foo/bar?namespace=default",
expectedSet: "/api/version/foo/bar?namespace=default",
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handleLinker(map[string]RESTStorage{"foo": &storage}, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -1102,7 +1203,7 @@ func TestCreate(t *testing.T) {
Other: "bar",
}
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 {
t.Errorf("unexpected error: %v", err)
}
@@ -1147,11 +1248,9 @@ func TestCreateInNamespace(t *testing.T) {
t: t,
name: "bar",
namespace: "other",
expectedSet: "/prefix/version/foo/bar?namespace=other",
expectedSet: "/api/version/foo/bar?namespace=other",
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handleLinker(map[string]RESTStorage{"foo": &storage}, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -1160,7 +1259,7 @@ func TestCreateInNamespace(t *testing.T) {
Other: "bar",
}
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 {
t.Errorf("unexpected error: %v", err)
}
@@ -1205,11 +1304,9 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
t: t,
name: "bar",
namespace: "other",
expectedSet: "/prefix/version/foo/bar?namespace=other",
expectedSet: "/api/version/foo/bar?namespace=other",
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
handler := handleInternal(map[string]RESTStorage{"foo": &storage}, deny.NewAlwaysDeny(), mapper, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -1218,7 +1315,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
Other: "bar",
}
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 {
t.Errorf("unexpected error: %v", err)
}
@@ -1269,11 +1366,11 @@ func TestDelayReturnsError(t *testing.T) {
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)
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 {
t.Errorf("Unexpected status %#v", status)
}
@@ -1333,15 +1430,15 @@ func TestCreateTimeout(t *testing.T) {
return obj, nil
},
}
handler := Handle(map[string]RESTStorage{
handler := handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
})
server := httptest.NewServer(handler)
defer server.Close()
simple := &Simple{Other: "foo"}
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 {
t.Errorf("Unexpected status %#v", itemOut)
}
@@ -1367,7 +1464,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
}
handler := CORS(
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper),
handle(map[string]RESTStorage{}),
allowedOriginRegexps, nil, nil, "true",
)
server := httptest.NewServer(handler)

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ limitations under the License.
package apiserver
import (
"fmt"
"net/http"
"net/url"
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.
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) {
w := res.ResponseWriter
@@ -109,13 +110,7 @@ func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runti
ctx := ctxFn(req)
ctx = api.WithNamespace(ctx, namespace)
requestInfo, err := requestInfoResolver.GetAPIRequestInfo(req.Request)
if err != nil {
errorJSON(err, codec, w)
return
}
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), requestInfo.APIVersion, requestInfo.Resource)
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), version, apiResource)
if err != nil {
errorJSON(err, codec, w)
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.
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) {
w := res.ResponseWriter
@@ -158,6 +153,7 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec ru
obj := r.New()
if err := codec.DecodeInto(body, obj); err != nil {
err = transformDecodeError(typer, err, obj, body)
errorJSON(err, codec, w)
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
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) {
w := res.ResponseWriter
@@ -213,6 +209,7 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, namer ScopeNamer, codec ru
obj := r.New()
if err := codec.DecodeInto(body, obj); err != nil {
err = transformDecodeError(typer, err, obj, body)
errorJSON(err, codec, w)
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
// plus the path and query generated by the provided linkFunc
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) {
simpleStorage := &SimpleRESTStorage{}
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(map[string]RESTStorage{"foo": simpleStorage})
server := httptest.NewServer(handler)
defer server.Close()
@@ -103,9 +101,7 @@ func TestWatchWebsocket(t *testing.T) {
func TestWatchHTTP(t *testing.T) {
simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(map[string]RESTStorage{"foo": simpleStorage})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
@@ -170,9 +166,7 @@ func TestWatchParamParsing(t *testing.T) {
return label, value, nil
})
simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/api", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(map[string]RESTStorage{"foo": simpleStorage})
server := httptest.NewServer(handler)
defer server.Close()
@@ -242,9 +236,7 @@ func TestWatchParamParsing(t *testing.T) {
func TestWatchProtocolSelection(t *testing.T) {
simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
handler := handle(map[string]RESTStorage{"foo": simpleStorage})
server := httptest.NewServer(handler)
defer server.Close()
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})
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
Create(pod *api.Pod) (*api.Pod, error)
Update(pod *api.Pod) (*api.Pod, error)
Bind(binding *api.Binding) error
}
// 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)
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
resource string
resourceName string
subresource string
selector labels.Selector
timeout time.Duration
@@ -166,6 +167,21 @@ func (r *Request) Resource(resource string) *Request {
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>)
func (r *Request) Name(resourceName string) *Request {
if r.err != nil {
@@ -360,8 +376,8 @@ func (r *Request) finalURL() string {
p = path.Join(p, resource)
}
// 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 {
p = path.Join(p, r.resourceName, r.subpath)
if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 {
p = path.Join(p, r.resourceName, r.subresource, r.subpath)
}
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) {
if (&Request{}).Name("bar").Name("baz").err == nil {
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 {
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) {

View File

@@ -30,7 +30,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"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/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"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/secret"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/ui"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@@ -408,6 +406,7 @@ func (m *Master) init(c *Config) {
m.storage = map[string]apiserver.RESTStorage{
"pods": podStorage,
"pods/status": podStatusStorage,
"pods/binding": bindingStorage,
"bindings": bindingStorage,
"replicationControllers": controller.NewREST(registry, podRegistry),
@@ -425,14 +424,14 @@ func (m *Master) init(c *Config) {
}
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)
}
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)
}
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)
}
apiVersions = []string{"v1beta1", "v1beta2", "v1beta3"}
@@ -569,26 +568,49 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
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.
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)
for k, v := range m.storage {
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.
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)
for k, v := range m.storage {
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.
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)
for k, v := range m.storage {
if k == "minions" {
@@ -596,5 +618,9 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec,
}
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"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"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.
func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) {
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", "")
out = &api.Status{Status: api.StatusSuccess}
return

View File

@@ -817,7 +817,10 @@ func TestEtcdCreate(t *testing.T) {
}
// 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 {
t.Fatalf("unexpected error: %v", err)
}
@@ -865,7 +868,10 @@ func TestEtcdCreateBindingNoPod(t *testing.T) {
// - Create (apiserver)
// - Schedule (scheduler)
// - 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 {
t.Fatalf("Expected not-found-error but got nothing")
}
@@ -935,7 +941,10 @@ func TestEtcdCreateWithContainersError(t *testing.T) {
}
// 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) {
t.Fatalf("Unexpected error returned: %#v", err)
}
@@ -973,7 +982,10 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) {
}
// 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 {
t.Fatalf("unexpected error: %v", err)
}
@@ -1025,7 +1037,10 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
}
// 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 {
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) {
registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext()

View File

@@ -45,6 +45,11 @@ type ObjectTyper interface {
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
// the resource version from an API object.
type ResourceVersioner interface {

View File

@@ -287,9 +287,11 @@ type binder struct {
// Bind just does a POST binding RPC.
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)
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 {

View File

@@ -366,9 +366,11 @@ func TestBind(t *testing.T) {
{binding: &api.Binding{
ObjectMeta: api.ObjectMeta{
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
}
b := &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: pod.Namespace},
PodID: pod.Name,
Host: dest,
ObjectMeta: api.ObjectMeta{Namespace: pod.Namespace, Name: pod.Name},
Target: api.ObjectReference{
Kind: "Node",
Name: dest,
},
}
if err := s.config.Binder.Bind(b); err != nil {
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/client/record"
"github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
type fakeBinder struct {
@@ -63,7 +64,7 @@ func TestScheduler(t *testing.T) {
{
sendPod: podWithID("foo"),
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",
}, {
sendPod: podWithID("foo"),
@@ -74,7 +75,7 @@ func TestScheduler(t *testing.T) {
}, {
sendPod: podWithID("foo"),
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,
expectError: errB,
expectErrorPod: podWithID("foo"),
@@ -120,7 +121,7 @@ func TestScheduler(t *testing.T) {
t.Errorf("%v: error: wanted %v, got %v", i, 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
events.Stop()