Skip to content

Commit

Permalink
Merge pull request kubernetes#5054 from smarterclayton/switch_to_sub_…
Browse files Browse the repository at this point in the history
…bindings

Make Pod binding a subresource (and be generic to other types)
  • Loading branch information
bgrant0607 committed Mar 9, 2015
2 parents 9e25317 + dfc1918 commit 7b72d95
Show file tree
Hide file tree
Showing 29 changed files with 557 additions and 523 deletions.
4 changes: 4 additions & 0 deletions pkg/api/testing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
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.
Expand Down
18 changes: 18 additions & 0 deletions pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions pkg/api/v1beta2/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 5 additions & 7 deletions pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
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.
Expand Down
118 changes: 60 additions & 58 deletions pkg/apiserver/api_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,15 @@ 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"
)

type APIInstaller struct {
group *APIGroupVersion
prefix string // Path prefix where API resources are to be registered.
version string // The API version being installed.
group *APIGroupVersion
info *APIRequestInfoResolver
prefix string // Path prefix where API resources are to be registered.
}

// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc).
Expand All @@ -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)
}
Expand All @@ -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) {
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -184,54 +181,59 @@ 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).
// v1beta1/v1beta2 format where namespace was a query parameter
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)
}
}

Expand All @@ -256,39 +258,39 @@ 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).
Writes(versionedObject)
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).
Writes(versionedList)
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).
Reads(versionedObject)
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).
Reads(versionedObject)
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)
Expand Down
Loading

0 comments on commit 7b72d95

Please sign in to comment.