Merge pull request #4395 from smarterclayton/split_naming

Fix cross-namespace LIST and WATCH
This commit is contained in:
Brian Grant 2015-02-17 09:40:09 -08:00
commit 876d651737
15 changed files with 777 additions and 309 deletions

View File

@ -19,6 +19,7 @@ limitations under the License.
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
@ -278,18 +279,19 @@ func runReplicationControllerTest(c *client.Client) {
}
glog.Infof("Creating replication controllers")
if _, err := c.ReplicationControllers(api.NamespaceDefault).Create(&controller); err != nil {
updated, err := c.ReplicationControllers("test").Create(&controller)
if err != nil {
glog.Fatalf("Unexpected error: %v", err)
}
glog.Infof("Done creating replication controllers")
// Give the controllers some time to actually create the pods
if err := wait.Poll(time.Second, time.Second*30, client.ControllerHasDesiredReplicas(c, &controller)); err != nil {
if err := wait.Poll(time.Second, time.Second*30, client.ControllerHasDesiredReplicas(c, updated)); err != nil {
glog.Fatalf("FAILED: pods never created %v", err)
}
// wait for minions to indicate they have info about the desired pods
pods, err := c.Pods(api.NamespaceDefault).List(labels.Set(controller.Spec.Selector).AsSelector())
pods, err := c.Pods("test").List(labels.Set(updated.Spec.Selector).AsSelector())
if err != nil {
glog.Fatalf("FAILED: unable to get pods to list: %v", err)
}
@ -311,12 +313,15 @@ func runAPIVersionsTest(c *client.Client) {
glog.Infof("Version test passed")
}
func runSelfLinkTest(c *client.Client) {
func runSelfLinkTestOnNamespace(c *client.Client, namespace string) {
var svc api.Service
err := c.Post().Resource("services").Body(
err := c.Post().
NamespaceIfScoped(namespace, len(namespace) > 0).
Resource("services").Body(
&api.Service{
ObjectMeta: api.ObjectMeta{
Name: "selflinktest",
Name: "selflinktest",
Namespace: namespace,
Labels: map[string]string{
"name": "selflinktest",
},
@ -335,18 +340,19 @@ func runSelfLinkTest(c *client.Client) {
if err != nil {
glog.Fatalf("Failed creating selflinktest service: %v", err)
}
err = c.Get().AbsPath(svc.SelfLink).Do().Into(&svc)
// TODO: this is not namespace aware
err = c.Get().RequestURI(svc.SelfLink).Do().Into(&svc)
if err != nil {
glog.Fatalf("Failed listing service with supplied self link '%v': %v", svc.SelfLink, err)
}
var svcList api.ServiceList
err = c.Get().Resource("services").Do().Into(&svcList)
err = c.Get().NamespaceIfScoped(namespace, len(namespace) > 0).Resource("services").Do().Into(&svcList)
if err != nil {
glog.Fatalf("Failed listing services: %v", err)
}
err = c.Get().AbsPath(svcList.SelfLink).Do().Into(&svcList)
err = c.Get().RequestURI(svcList.SelfLink).Do().Into(&svcList)
if err != nil {
glog.Fatalf("Failed listing services with supplied self link '%v': %v", svcList.SelfLink, err)
}
@ -358,16 +364,16 @@ func runSelfLinkTest(c *client.Client) {
continue
}
found = true
err = c.Get().AbsPath(item.SelfLink).Do().Into(&svc)
err = c.Get().RequestURI(item.SelfLink).Do().Into(&svc)
if err != nil {
glog.Fatalf("Failed listing service with supplied self link '%v': %v", item.SelfLink, err)
}
break
}
if !found {
glog.Fatalf("never found selflinktest service")
glog.Fatalf("never found selflinktest service in namespace %s", namespace)
}
glog.Infof("Self link test passed")
glog.Infof("Self link test passed in namespace %s", namespace)
// TODO: Should test PUT at some point, too.
}
@ -518,7 +524,7 @@ func runMasterServiceTest(client *client.Client) {
}
func runServiceTest(client *client.Client) {
pod := api.Pod{
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
@ -543,14 +549,14 @@ func runServiceTest(client *client.Client) {
PodIP: "1.2.3.4",
},
}
_, err := client.Pods(api.NamespaceDefault).Create(&pod)
pod, err := client.Pods(api.NamespaceDefault).Create(pod)
if err != nil {
glog.Fatalf("Failed to create pod: %v, %v", pod, err)
}
if err := wait.Poll(time.Second, time.Second*20, podExists(client, pod.Namespace, pod.Name)); err != nil {
glog.Fatalf("FAILED: pod never started running %v", err)
}
svc1 := api.Service{
svc1 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "service1"},
Spec: api.ServiceSpec{
Selector: map[string]string{
@ -561,15 +567,33 @@ func runServiceTest(client *client.Client) {
SessionAffinity: "None",
},
}
_, err = client.Services(api.NamespaceDefault).Create(&svc1)
svc1, err = client.Services(api.NamespaceDefault).Create(svc1)
if err != nil {
glog.Fatalf("Failed to create service: %v, %v", svc1, err)
}
// create an identical service in the default namespace
svc3 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "service1"},
Spec: api.ServiceSpec{
Selector: map[string]string{
"name": "thisisalonglabel",
},
Port: 8080,
Protocol: "TCP",
SessionAffinity: "None",
},
}
svc3, err = client.Services("other").Create(svc3)
if err != nil {
glog.Fatalf("Failed to create service: %v, %v", svc3, err)
}
if err := wait.Poll(time.Second, time.Second*20, endpointsSet(client, svc1.Namespace, svc1.Name, 1)); err != nil {
glog.Fatalf("FAILED: unexpected endpoints: %v", err)
}
// A second service with the same port.
svc2 := api.Service{
svc2 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "service2"},
Spec: api.ServiceSpec{
Selector: map[string]string{
@ -580,13 +604,30 @@ func runServiceTest(client *client.Client) {
SessionAffinity: "None",
},
}
_, err = client.Services(api.NamespaceDefault).Create(&svc2)
svc2, err = client.Services(api.NamespaceDefault).Create(svc2)
if err != nil {
glog.Fatalf("Failed to create service: %v, %v", svc2, err)
}
if err := wait.Poll(time.Second, time.Second*20, endpointsSet(client, svc2.Namespace, svc2.Name, 1)); err != nil {
glog.Fatalf("FAILED: unexpected endpoints: %v", err)
}
if ok, err := endpointsSet(client, svc3.Namespace, svc3.Name, 0)(); !ok || err != nil {
glog.Fatalf("FAILED: service in other namespace should have no endpoints: %v %v", ok, err)
}
svcList, err := client.Services(api.NamespaceAll).List(labels.Everything())
if err != nil {
glog.Fatalf("Failed to list services across namespaces: %v", err)
}
names := util.NewStringSet()
for _, svc := range svcList.Items {
names.Insert(fmt.Sprintf("%s/%s", svc.Namespace, svc.Name))
}
if !names.HasAll("default/kubernetes", "default/kubernetes-ro", "default/service1", "default/service2", "other/service1") {
glog.Fatalf("Unexpected service list: %#v", names)
}
glog.Info("Service test passed.")
}
@ -623,7 +664,10 @@ func main() {
runServiceTest,
runAPIVersionsTest,
runMasterServiceTest,
runSelfLinkTest,
func(c *client.Client) {
runSelfLinkTestOnNamespace(c, "")
runSelfLinkTestOnNamespace(c, "other")
},
}
var wg sync.WaitGroup
wg.Add(len(testFuncs))

View File

@ -156,6 +156,11 @@ for version in "${kube_api_versions[@]}"; do
howmanypods="$(kubectl get pods -o template -t "{{ len .items }}" "${kube_flags[@]}")"
[ "$howmanypods" -eq 0 ]
# make calls in another namespace
kubectl create --namespace=other -f examples/guestbook/redis-master.json "${kube_flags[@]}"
kubectl get pod --namespace=other redis-master
kubectl delete pod --namespace=other redis-master
kube::log::status "Testing kubectl(${version}:services)"
kubectl get services "${kube_flags[@]}"
kubectl create -f examples/guestbook/frontend-service.json "${kube_flags[@]}"

View File

@ -42,6 +42,7 @@ type action struct {
Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc).
Path string // The path of the action
Params []*restful.Parameter // List of parameters associated with the action.
Namer ScopeNamer
}
// errEmptyName is returned when API requests do not fill the name section of the path.
@ -87,7 +88,6 @@ func (a *APIInstaller) newWebService() *restful.WebService {
func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler http.Handler, redirectHandler http.Handler, proxyHandler http.Handler) error {
codec := a.group.codec
admit := a.group.admit
linker := a.group.linker
context := a.group.context
resource := path
@ -149,13 +149,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
}
var ctxFn ContextFunc
var namespaceFn ResourceNamespaceFunc
var nameFn ResourceNameFunc
var generateLinkFn linkFunc
var objNameFn ObjectNameFunc
linkFn := func(req *restful.Request, obj runtime.Object) error {
return setSelfLink(obj, req.Request, a.group.linker, generateLinkFn)
}
ctxFn = func(req *restful.Request) api.Context {
if ctx, ok := context.Get(req.Request); ok {
return ctx
@ -168,146 +161,76 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
params := []*restful.Parameter{}
actions := []action{}
// Get the list of actions for the given scope.
if scope.Name() != meta.RESTScopeNameNamespace {
objNameFn = func(obj runtime.Object) (namespace, name string, err error) {
name, err = linker.Name(obj)
if len(name) == 0 {
err = errEmptyName
}
return
}
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
actions = appendIf(actions, action{"LIST", path, params}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"POST", path, params}, storageVerbs["RESTCreater"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params}, allowWatchList)
itemPath := path + "/{name}"
nameParams := append(params, nameParam)
namespaceFn = func(req *restful.Request) (namespace string, err error) {
return
}
nameFn = func(req *restful.Request) (namespace, name string, err error) {
name = req.PathParameter("name")
if len(name) == 0 {
err = errEmptyName
}
return
}
generateLinkFn = func(namespace, name string) (path string, query string) {
path = strings.Replace(itemPath, "{name}", name, 1)
path = gpath.Join(a.prefix, path)
return
}
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", path, params, namer}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"POST", path, params, namer}, storageVerbs["RESTCreater"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, storageVerbs["RESTGetter"])
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, storageVerbs["RESTUpdater"])
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, storageVerbs["RESTDeleter"])
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, storageVerbs["ResourceWatcher"])
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams}, storageVerbs["ResourceWatcher"])
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
} else {
objNameFn = func(obj runtime.Object) (namespace, name string, err error) {
if name, err = linker.Name(obj); err != nil {
return
}
namespace, err = linker.Namespace(obj)
return
}
// v1beta3 format with namespace in path
if scope.ParamPath() {
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path
namespaceParams := []*restful.Parameter{namespaceParam}
namespaceFn = func(req *restful.Request) (namespace string, err error) {
namespace = req.PathParameter(scope.ParamName())
if len(namespace) == 0 {
namespace = api.NamespaceDefault
}
return
}
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams}, storageVerbs["RESTCreater"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams}, allowWatchList)
itemPath := namespacedPath + "/{name}"
nameParams := append(namespaceParams, nameParam)
nameFn = func(req *restful.Request) (namespace, name string, err error) {
namespace, _ = namespaceFn(req)
name = req.PathParameter("name")
if len(name) == 0 {
err = errEmptyName
}
return
}
generateLinkFn = func(namespace, name string) (path string, query string) {
path = strings.Replace(itemPath, "{name}", name, 1)
path = strings.Replace(path, "{"+scope.ParamName()+"}", namespace, 1)
path = gpath.Join(a.prefix, path)
return
}
namer := scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), false}
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams}, storageVerbs["ResourceWatcher"])
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams, namer}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams, namer}, storageVerbs["RESTCreater"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, storageVerbs["RESTGetter"])
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, storageVerbs["RESTUpdater"])
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, storageVerbs["RESTDeleter"])
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, storageVerbs["ResourceWatcher"])
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
// list across namespace.
actions = appendIf(actions, action{"LIST", path, params}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params}, allowWatchList)
namer = scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), true}
actions = appendIf(actions, action{"LIST", path, params, namer}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, 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}
namespaceFn = func(req *restful.Request) (namespace string, err error) {
namespace = req.QueryParameter(scope.ParamName())
if len(namespace) == 0 {
namespace = api.NamespaceDefault
}
return
}
actions = appendIf(actions, action{"LIST", path, namespaceParams}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"POST", path, namespaceParams}, storageVerbs["RESTCreater"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams}, allowWatchList)
itemPath := path + "/{name}"
nameParams := append(namespaceParams, nameParam)
nameFn = func(req *restful.Request) (namespace, name string, err error) {
namespace, _ = namespaceFn(req)
name = req.PathParameter("name")
if len(name) == 0 {
err = errEmptyName
}
return
}
generateLinkFn = func(namespace, name string) (path string, query string) {
path = strings.Replace(itemPath, "{name}", name, -1)
path = gpath.Join(a.prefix, path)
if len(namespace) > 0 {
values := make(url.Values)
values.Set(scope.ParamName(), namespace)
query = values.Encode()
}
return
}
namer := legacyScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)}
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams}, storageVerbs["ResourceWatcher"])
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"LIST", path, namespaceParams, namer}, storageVerbs["RESTLister"])
actions = appendIf(actions, action{"POST", path, namespaceParams, namer}, storageVerbs["RESTCreater"])
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams, namer}, allowWatchList)
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, storageVerbs["RESTGetter"])
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, storageVerbs["RESTUpdater"])
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, storageVerbs["RESTDeleter"])
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, storageVerbs["ResourceWatcher"])
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, storageVerbs["Redirector"])
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
}
}
@ -332,7 +255,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, nameFn, linkFn, codec)).
route := ws.GET(action.Path).To(GetResource(getter, ctxFn, action.Namer, codec)).
Filter(m).
Doc("read the specified " + kind).
Operation("read" + kind).
@ -340,7 +263,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, namespaceFn, linkFn, codec)).
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, codec)).
Filter(m).
Doc("list objects of kind " + kind).
Operation("list" + kind).
@ -348,7 +271,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, nameFn, objNameFn, linkFn, codec, resource, admit)).
route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, action.Namer, codec, resource, admit)).
Filter(m).
Doc("update the specified " + kind).
Operation("update" + kind).
@ -356,7 +279,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, namespaceFn, linkFn, codec, resource, admit)).
route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, action.Namer, codec, resource, admit)).
Filter(m).
Doc("create a " + kind).
Operation("create" + kind).
@ -364,7 +287,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, nameFn, linkFn, codec, resource, kind, admit)).
route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, action.Namer, codec, resource, kind, admit)).
Filter(m).
Doc("delete a " + kind).
Operation("delete" + kind)
@ -409,6 +332,224 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
return nil
}
// rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer
// for root scoped resources.
type rootScopeNaming struct {
scope meta.RESTScope
runtime.SelfLinker
itemPath string
}
// rootScopeNaming implements ScopeNamer
var _ ScopeNamer = rootScopeNaming{}
// Namespace returns an empty string because root scoped objects have no namespace.
func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
return "", nil
}
// Name returns the name from the path and an empty string for namespace, or an error if the
// name is empty.
func (n rootScopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
name = req.PathParameter("name")
if len(name) == 0 {
return "", "", errEmptyName
}
return "", name, nil
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func (n rootScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) {
_, name, err := n.ObjectName(obj)
if err != nil {
return "", "", err
}
if len(name) == 0 {
_, name, err = n.Name(req)
if err != nil {
return "", "", err
}
}
path = strings.Replace(n.itemPath, "{name}", name, 1)
return path, "", nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func (n rootScopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
path = req.Request.URL.Path
return path, "", nil
}
// ObjectName returns the name set on the object, or an error if the
// name cannot be returned. Namespace is empty
// TODO: distinguish between objects with name/namespace and without via a specific error.
func (n rootScopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
name, err = n.SelfLinker.Name(obj)
if err != nil {
return "", "", err
}
if len(name) == 0 {
return "", "", errEmptyName
}
return "", name, nil
}
// scopeNaming returns naming information from a request. It implements ScopeNamer for
// namespace scoped resources.
type scopeNaming struct {
scope meta.RESTScope
runtime.SelfLinker
itemPath string
allNamespaces bool
}
// scopeNaming implements ScopeNamer
var _ ScopeNamer = scopeNaming{}
// Namespace returns the namespace from the path or the default.
func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
if n.allNamespaces {
return "", nil
}
namespace = req.PathParameter(n.scope.ParamName())
if len(namespace) == 0 {
// a URL was constructed without the namespace, or this method was invoked
// on an object without a namespace path parameter.
return "", fmt.Errorf("no namespace parameter found on request")
}
return namespace, nil
}
// Name returns the name from the path, the namespace (or default), or an error if the
// name is empty.
func (n scopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
namespace, _ = n.Namespace(req)
name = req.PathParameter("name")
if len(name) == 0 {
return "", "", errEmptyName
}
return
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func (n scopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) {
namespace, name, err := n.ObjectName(obj)
if err != nil {
return "", "", err
}
if len(namespace) == 0 && len(name) == 0 {
namespace, name, err = n.Name(req)
if err != nil {
return "", "", err
}
}
path = strings.Replace(n.itemPath, "{name}", name, 1)
if !n.allNamespaces {
path = strings.Replace(path, "{"+n.scope.ParamName()+"}", namespace, 1)
}
return path, "", nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func (n scopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
path = req.Request.URL.Path
return path, "", nil
}
// ObjectName returns the name and namespace set on the object, or an error if the
// name cannot be returned.
// TODO: distinguish between objects with name/namespace and without via a specific error.
func (n scopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
name, err = n.SelfLinker.Name(obj)
if err != nil {
return "", "", err
}
namespace, err = n.SelfLinker.Namespace(obj)
if err != nil {
return "", "", err
}
return namespace, name, err
}
// legacyScopeNaming modifies a scopeNaming to read namespace from the query. It implements
// ScopeNamer for older query based namespace parameters.
type legacyScopeNaming struct {
scope meta.RESTScope
runtime.SelfLinker
itemPath string
}
// legacyScopeNaming implements ScopeNamer
var _ ScopeNamer = legacyScopeNaming{}
// Namespace returns the namespace from the query or the default.
func (n legacyScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
values, ok := req.Request.URL.Query()[n.scope.ParamName()]
if !ok || len(values) == 0 {
// legacy behavior
if req.Request.Method == "POST" || len(req.PathParameter("name")) > 0 {
return api.NamespaceDefault, nil
}
return api.NamespaceAll, nil
}
return values[0], nil
}
// Name returns the name from the path, the namespace (or default), or an error if the
// name is empty.
func (n legacyScopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
namespace, _ = n.Namespace(req)
name = req.PathParameter("name")
if len(name) == 0 {
return "", "", errEmptyName
}
return namespace, name, nil
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func (n legacyScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) {
namespace, name, err := n.ObjectName(obj)
if err != nil {
return "", "", err
}
if len(name) == 0 {
return "", "", errEmptyName
}
path = strings.Replace(n.itemPath, "{name}", name, -1)
values := make(url.Values)
values.Set(n.scope.ParamName(), namespace)
query = values.Encode()
return path, query, nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func (n legacyScopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
namespace, err := n.Namespace(req)
if err != nil {
return "", "", err
}
path = req.Request.URL.Path
values := make(url.Values)
values.Set(n.scope.ParamName(), namespace)
query = values.Encode()
return path, query, nil
}
// ObjectName returns the name and namespace set on the object, or an error if the
// name cannot be returned.
// TODO: distinguish between objects with name/namespace and without via a specific error.
func (n legacyScopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
name, err = n.SelfLinker.Name(obj)
if err != nil {
return "", "", err
}
namespace, err = n.SelfLinker.Namespace(obj)
if err != nil {
return "", "", err
}
return namespace, name, err
}
// This magic incantation returns *ptrToObject for an arbitrary pointer
func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()

View File

@ -154,6 +154,9 @@ type SimpleRESTStorage struct {
updated *Simple
created *Simple
actualNamespace string
namespacePresent bool
// These are set when Watch is called
fakeWatch *watch.FakeWatcher
requestedLabelSelector labels.Selector
@ -172,6 +175,7 @@ type SimpleRESTStorage struct {
}
func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
storage.checkContext(ctx)
result := &SimpleList{
Items: storage.list,
}
@ -179,10 +183,16 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Sele
}
func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
storage.checkContext(ctx)
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
}
func (storage *SimpleRESTStorage) checkContext(ctx api.Context) {
storage.actualNamespace, storage.namespacePresent = api.NamespaceFrom(ctx)
}
func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (runtime.Object, error) {
storage.checkContext(ctx)
storage.deleted = id
if err := storage.errors["delete"]; err != nil {
return nil, err
@ -204,6 +214,7 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object {
}
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
storage.checkContext(ctx)
storage.created = obj.(*Simple)
if err := storage.errors["create"]; err != nil {
return nil, err
@ -216,6 +227,7 @@ func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (r
}
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
storage.checkContext(ctx)
storage.updated = obj.(*Simple)
if err := storage.errors["update"]; err != nil {
return nil, false, err
@ -229,6 +241,7 @@ func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (r
// Implement ResourceWatcher.
func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
storage.checkContext(ctx)
storage.requestedLabelSelector = label
storage.requestedFieldSelector = field
storage.requestedResourceVersion = resourceVersion
@ -242,6 +255,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
// Implement Redirector.
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
storage.checkContext(ctx)
// validate that the namespace context on the request matches the expected input
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
@ -388,29 +402,57 @@ func TestVersion(t *testing.T) {
}
}
func TestSimpleList(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
namespace: "other",
expectedSet: "/prefix/version/simple?namespace=other",
func TestList(t *testing.T) {
testCases := []struct {
url string
namespace string
selfLink string
legacy bool
}{
{"/prefix/version/simple", "", "/prefix/version/simple?namespace=", true},
{"/prefix/version/simple?namespace=other", "other", "/prefix/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},
// list items across all namespaces
{"/prefix/version/simple", "", "/prefix/version/simple", false},
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
for i, testCase := range testCases {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace}
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
namespace: testCase.namespace,
expectedSet: testCase.selfLink,
}
var handler http.Handler
if testCase.legacy {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
} else {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper)
}
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
}
if !selfLinker.called {
t.Errorf("Never set self link")
resp, err := http.Get(server.URL + testCase.url)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
if resp.StatusCode != http.StatusOK {
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
}
// TODO: future, restore get links
if !selfLinker.called {
t.Errorf("%d: never set self link", i)
}
if !simpleStorage.namespacePresent {
t.Errorf("%d: namespace not set", i)
} else if simpleStorage.actualNamespace != testCase.namespace {
t.Errorf("%d: unexpected resource namespace: %s", i, simpleStorage.actualNamespace)
}
}
}
@ -426,7 +468,7 @@ func TestErrorList(t *testing.T) {
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusInternalServerError {
@ -439,7 +481,54 @@ func TestNonEmptyList(t *testing.T) {
simpleStorage := SimpleRESTStorage{
list: []Simple{
{
TypeMeta: api.TypeMeta{Kind: "Simple"},
ObjectMeta: api.ObjectMeta{Name: "something", Namespace: "other"},
Other: "foo",
},
},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
body, _ := ioutil.ReadAll(resp.Body)
t.Logf("Data: %s", string(body))
}
var listOut SimpleList
body, err := extractBody(resp, &listOut)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(listOut.Items) != 1 {
t.Errorf("Unexpected response: %#v", listOut)
return
}
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=" {
t.Errorf("unexpected list self link: %#v", listOut)
}
expectedSelfLink := "/prefix/version/simple/something?namespace=other"
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
}
}
func TestSelfLinkSkipsEmptyName(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
list: []Simple{
{
ObjectMeta: api.ObjectMeta{Namespace: "other"},
Other: "foo",
},
@ -452,7 +541,7 @@ func TestNonEmptyList(t *testing.T) {
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
@ -460,11 +549,10 @@ func TestNonEmptyList(t *testing.T) {
body, _ := ioutil.ReadAll(resp.Body)
t.Logf("Data: %s", string(body))
}
var listOut SimpleList
body, err := extractBody(resp, &listOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if len(listOut.Items) != 1 {
@ -474,7 +562,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))
}
expectedSelfLink := "/prefix/version/simple?namespace=other"
if listOut.SelfLink != "/prefix/version/simple?namespace=" {
t.Errorf("unexpected list self link: %#v", listOut)
}
expectedSelfLink := ""
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
}
@ -817,6 +908,48 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
}
}
// when the object name and namespace can't be retrieved, skip name checking
func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
err: fmt.Errorf("test error"),
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
Namespace: "other", // does not match request
},
Other: "bar",
}
body, err := codec.Encode(item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
}
if selfLinker.called {
t.Errorf("self link ignored")
}
}
func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
@ -931,20 +1064,79 @@ type setTestSelfLinker struct {
name string
namespace string
called bool
err error
}
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, nil }
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, nil }
func (*setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", nil }
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err }
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, s.err }
func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", s.err }
func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error {
if e, a := s.expectedSet, selfLink; e != a {
s.t.Errorf("expected '%v', got '%v'", e, a)
}
s.called = true
return nil
return s.err
}
func TestCreate(t *testing.T) {
storage := SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
time.Sleep(5 * time.Millisecond)
return obj, nil
},
}
selfLinker := &setTestSelfLinker{
t: t,
name: "bar",
namespace: "default",
expectedSet: "/prefix/version/foo/bar?namespace=default",
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
simple := &Simple{
Other: "bar",
}
data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
wg := sync.WaitGroup{}
wg.Add(1)
var response *http.Response
go func() {
response, err = client.Do(request)
wg.Done()
}()
wg.Wait()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var itemOut Simple
body, err := extractBody(response, &itemOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(&itemOut, simple) {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
}
if response.StatusCode != http.StatusCreated {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
}
if !selfLinker.called {
t.Errorf("Never set self link")
}
}
func TestCreateInNamespace(t *testing.T) {
storage := SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
time.Sleep(5 * time.Millisecond)

View File

@ -18,6 +18,7 @@ package apiserver
import (
"net/http"
gpath "path"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
@ -27,66 +28,70 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
)
// ContextFunc returns a Context given a request - a context must be returned
type ContextFunc func(req *restful.Request) api.Context
// ResourceNameFunc returns a name (and optional namespace) given a request - if no name is present
// an error must be returned.
type ResourceNameFunc func(req *restful.Request) (namespace, name string, err error)
// ObjectNameFunc returns the name (and optional namespace) of an object
type ObjectNameFunc func(obj runtime.Object) (namespace, name string, err error)
// ResourceNamespaceFunc returns the namespace associated with the given request - if no namespace
// is present an error must be returned.
type ResourceNamespaceFunc func(req *restful.Request) (namespace string, err error)
// LinkResourceFunc updates the provided object with a SelfLink that is appropriate for the current
// request.
type LinkResourceFunc func(req *restful.Request, obj runtime.Object) error
// ScopeNamer handles accessing names from requests and objects
type ScopeNamer interface {
// Namespace returns the appropriate namespace value from the request (may be empty) or an
// error.
Namespace(req *restful.Request) (namespace string, err error)
// Name returns the name from the request, and an optional namespace value if this is a namespace
// scoped call. An error is returned if the name is not available.
Name(req *restful.Request) (namespace, name string, err error)
// ObjectName returns the namespace and name from an object if they exist, or an error if the object
// does not support names.
ObjectName(obj runtime.Object) (namespace, name string, err error)
// SetSelfLink sets the provided URL onto the object. The method should return nil if the object
// does not support selfLinks.
SetSelfLink(obj runtime.Object, url string) error
// GenerateLink creates a path and query for a given runtime object that represents the canonical path.
GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error)
// GenerateLink creates a path and query for a list that represents the canonical path.
GenerateListLink(req *restful.Request) (path, query string, err error)
}
// GetResource returns a function that handles retrieving a single resource from a RESTStorage object.
func GetResource(r RESTGetter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
func GetResource(r RESTGetter, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter
namespace, name, err := nameFn(req)
namespace, name, err := namer.Name(req)
if err != nil {
notFound(w, req.Request)
return
}
ctx := ctxFn(req)
if len(namespace) > 0 {
ctx = api.WithNamespace(ctx, namespace)
}
item, err := r.Get(ctx, name)
ctx = api.WithNamespace(ctx, namespace)
result, err := r.Get(ctx, name)
if err != nil {
errorJSON(err, codec, w)
return
}
if err := linkFn(req, item); err != nil {
if err := setSelfLink(result, req, namer); err != nil {
errorJSON(err, codec, w)
return
}
writeJSON(http.StatusOK, codec, item, w)
writeJSON(http.StatusOK, codec, result, w)
}
}
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter
namespace, err := namespaceFn(req)
namespace, err := namer.Namespace(req)
if err != nil {
notFound(w, req.Request)
return
}
ctx := ctxFn(req)
if len(namespace) > 0 {
ctx = api.WithNamespace(ctx, namespace)
}
ctx = api.WithNamespace(ctx, namespace)
label, err := labels.ParseSelector(req.Request.URL.Query().Get("labels"))
if err != nil {
errorJSON(err, codec, w)
@ -98,36 +103,34 @@ func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespace
return
}
item, err := r.List(ctx, label, field)
result, err := r.List(ctx, label, field)
if err != nil {
errorJSON(err, codec, w)
return
}
if err := linkFn(req, item); err != nil {
if err := setListSelfLink(result, req, namer); err != nil {
errorJSON(err, codec, w)
return
}
writeJSON(http.StatusOK, codec, item, w)
writeJSON(http.StatusOK, codec, result, w)
}
}
// CreateResource returns a function that will handle a resource creation.
func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
namespace, err := namespaceFn(req)
namespace, err := namer.Namespace(req)
if err != nil {
notFound(w, req.Request)
return
}
ctx := ctxFn(req)
if len(namespace) > 0 {
ctx = api.WithNamespace(ctx, namespace)
}
ctx = api.WithNamespace(ctx, namespace)
body, err := readBody(req.Request)
if err != nil {
@ -159,7 +162,7 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamesp
return
}
if err := linkFn(req, result); err != nil {
if err := setSelfLink(result, req, namer); err != nil {
errorJSON(err, codec, w)
return
}
@ -169,22 +172,20 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamesp
}
// UpdateResource returns a function that will handle a resource update
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
namespace, name, err := nameFn(req)
namespace, name, err := namer.Name(req)
if err != nil {
notFound(w, req.Request)
return
}
ctx := ctxFn(req)
if len(namespace) > 0 {
ctx = api.WithNamespace(ctx, namespace)
}
ctx = api.WithNamespace(ctx, namespace)
body, err := readBody(req.Request)
if err != nil {
@ -198,20 +199,18 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, o
return
}
objNamespace, objName, err := objNameFunc(obj)
if err != nil {
errorJSON(err, codec, w)
return
}
if objName != name {
errorJSON(errors.NewBadRequest("the name of the object does not match the name on the URL"), codec, w)
return
}
if len(namespace) > 0 {
if len(objNamespace) > 0 && objNamespace != namespace {
errorJSON(errors.NewBadRequest("the namespace of the object does not match the namespace on the request"), codec, w)
// check the provided name against the request
if objNamespace, objName, err := namer.ObjectName(obj); err == nil {
if objName != name {
errorJSON(errors.NewBadRequest("the name of the object does not match the name on the URL"), codec, w)
return
}
if len(namespace) > 0 {
if len(objNamespace) > 0 && objNamespace != namespace {
errorJSON(errors.NewBadRequest("the namespace of the object does not match the namespace on the request"), codec, w)
return
}
}
}
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
@ -231,7 +230,7 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, o
return
}
if err := linkFn(req, result); err != nil {
if err := setSelfLink(result, req, namer); err != nil {
errorJSON(err, codec, w)
return
}
@ -245,14 +244,14 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, o
}
// DeleteResource returns a function that will handle a resource deletion
func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
func DeleteResource(r RESTDeleter, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
namespace, name, err := nameFn(req)
namespace, name, err := namer.Name(req)
if err != nil {
notFound(w, req.Request)
return
@ -290,7 +289,7 @@ func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, l
} else {
// when a non-status response is returned, set the self link
if _, ok := result.(*api.Status); !ok {
if err := linkFn(req, result); err != nil {
if err := setSelfLink(result, req, namer); err != nil {
errorJSON(err, codec, w)
return
}
@ -329,42 +328,58 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
}
}
type linkFunc func(namespace, name string) (path string, query string)
// 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 *http.Request, linker runtime.SelfLinker, fn linkFunc) error {
namespace, err := linker.Namespace(obj)
func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
// TODO: SelfLink generation should return a full URL?
path, query, err := namer.GenerateLink(req, obj)
if err == errEmptyName {
return nil
}
if err != nil {
return err
}
name, err := linker.Name(obj)
if err != nil {
return err
}
path, query := fn(namespace, name)
newURL := *req.URL
newURL.Path = path
newURL := *req.Request.URL
// use only canonical paths
newURL.Path = gpath.Clean(path)
newURL.RawQuery = query
newURL.Fragment = ""
if err := linker.SetSelfLink(obj, newURL.String()); err != nil {
return err
}
return namer.SetSelfLink(obj, newURL.String())
}
// setListSelfLink sets the self link of a list to the base URL, then sets the self links
// on all child objects returned.
func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
if !runtime.IsListType(obj) {
return nil
}
// TODO: List SelfLink generation should return a full URL?
path, query, err := namer.GenerateListLink(req)
if err != nil {
return err
}
newURL := *req.Request.URL
newURL.Path = path
newURL.RawQuery = query
// use the path that got us here
newURL.Fragment = ""
if err := namer.SetSelfLink(obj, newURL.String()); err != nil {
glog.V(4).Infof("Unable to set self link on object: %v", err)
}
// Set self-link of objects in the list.
items, err := runtime.ExtractList(obj)
if err != nil {
return err
}
for i := range items {
if err := setSelfLink(items[i], req, linker, fn); err != nil {
if err := setSelfLink(items[i], req, namer); err != nil {
return err
}
}
return runtime.SetList(obj, items)
}

View File

@ -42,7 +42,7 @@ func buildResourcePath(prefix, namespace, resource string) string {
base := path.Join("/api", testapi.Version(), prefix)
if len(namespace) > 0 {
if !(testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2") {
base = path.Join(base, "ns", namespace)
base = path.Join(base, "namespaces", namespace)
}
}
return path.Join(base, resource)
@ -58,10 +58,8 @@ func buildQueryValues(namespace string, query url.Values) url.Values {
}
}
}
if len(namespace) > 0 {
if testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2" {
v.Set("namespace", namespace)
}
if testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2" {
v.Set("namespace", namespace)
}
return v
}

View File

@ -66,7 +66,7 @@ func (e *events) Create(event *api.Event) (*api.Event, error) {
}
result := &api.Event{}
err := e.client.Post().
Namespace(event.Namespace).
NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0).
Resource("events").
Body(event).
Do().
@ -85,7 +85,7 @@ func (e *events) Update(event *api.Event) (*api.Event, error) {
}
result := &api.Event{}
err := e.client.Put().
Namespace(event.Namespace).
NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0).
Resource("events").
Name(event.Name).
Body(event).
@ -98,7 +98,7 @@ func (e *events) Update(event *api.Event) (*api.Event, error) {
func (e *events) List(label, field labels.Selector) (*api.EventList, error) {
result := &api.EventList{}
err := e.client.Get().
Namespace(e.namespace).
NamespaceIfScoped(e.namespace, len(e.namespace) > 0).
Resource("events").
SelectorParam("labels", label).
SelectorParam("fields", field).
@ -115,7 +115,7 @@ func (e *events) Get(name string) (*api.Event, error) {
result := &api.Event{}
err := e.client.Get().
Namespace(e.namespace).
NamespaceIfScoped(e.namespace, len(e.namespace) > 0).
Resource("events").
Name(name).
Do().
@ -127,7 +127,7 @@ func (e *events) Get(name string) (*api.Event, error) {
func (e *events) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
return e.client.Get().
Prefix("watch").
Namespace(e.namespace).
NamespaceIfScoped(e.namespace, len(e.namespace) > 0).
Resource("events").
Param("resourceVersion", resourceVersion).
SelectorParam("labels", label).

View File

@ -62,7 +62,9 @@ func TestEventCreate(t *testing.T) {
}
timeStamp := util.Now()
event := &api.Event{
//namespace: namespace{"default"},
ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceDefault,
},
InvolvedObject: *objReference,
FirstTimestamp: timeStamp,
LastTimestamp: timeStamp,
@ -80,7 +82,7 @@ func TestEventCreate(t *testing.T) {
response, err := c.Setup().Events("").Create(event)
if err != nil {
t.Errorf("%#v should be nil.", err)
t.Fatalf("%v should be nil.", err)
}
if e, a := *objReference, response.InvolvedObject; !reflect.DeepEqual(e, a) {
@ -99,6 +101,9 @@ func TestEventGet(t *testing.T) {
}
timeStamp := util.Now()
event := &api.Event{
ObjectMeta: api.ObjectMeta{
Namespace: "other",
},
InvolvedObject: *objReference,
FirstTimestamp: timeStamp,
LastTimestamp: timeStamp,
@ -116,7 +121,7 @@ func TestEventGet(t *testing.T) {
response, err := c.Setup().Events("").Get("1")
if err != nil {
t.Errorf("%#v should be nil.", err)
t.Fatalf("%v should be nil.", err)
}
if e, r := event.InvolvedObject, response.InvolvedObject; !reflect.DeepEqual(e, r) {

View File

@ -90,7 +90,7 @@ type Request struct {
// generic components accessible via method setters
path string
subpath string
params map[string]string
params url.Values
// structural elements of the request that are part of the Kubernetes API conventions
namespace string
@ -181,6 +181,14 @@ func (r *Request) Namespace(namespace string) *Request {
return r
}
// NamespaceIfScoped is a convenience function to set a namespace if scoped is true
func (r *Request) NamespaceIfScoped(namespace string, scoped bool) *Request {
if scoped {
return r.Namespace(namespace)
}
return r
}
// AbsPath overwrites an existing path with the segments provided. Trailing slashes are preserved
// when a single segment is passed.
func (r *Request) AbsPath(segments ...string) *Request {
@ -196,6 +204,29 @@ func (r *Request) AbsPath(segments ...string) *Request {
return r
}
// RequestURI overwrites existing path and parameters with the value of the provided server relative
// URI. Some parameters (those in specialParameters) cannot be overwritten.
func (r *Request) RequestURI(uri string) *Request {
if r.err != nil {
return r
}
locator, err := url.Parse(uri)
if err != nil {
r.err = err
return r
}
r.path = locator.Path
if len(locator.Query()) > 0 {
if r.params == nil {
r.params = make(url.Values)
}
for k, v := range locator.Query() {
r.params[k] = v
}
}
return r
}
// ParseSelectorParam parses the given string as a resource label selector.
// This is a convenience function so you don't have to first check that it's a
// validly formatted selector.
@ -244,9 +275,9 @@ func (r *Request) setParam(paramName, value string) *Request {
return r
}
if r.params == nil {
r.params = make(map[string]string)
r.params = make(url.Values)
}
r.params[paramName] = value
r.params[paramName] = []string{value}
return r
}
@ -317,16 +348,16 @@ func (r *Request) finalURL() string {
query := url.Values{}
for key, value := range r.params {
query.Add(key, value)
query[key] = value
}
if r.namespaceSet && r.namespaceInQuery && len(r.namespace) > 0 {
query.Add("namespace", r.namespace)
if r.namespaceSet && r.namespaceInQuery {
query.Set("namespace", r.namespace)
}
// timeout is handled specially here.
if r.timeout != 0 {
query.Add("timeout", r.timeout.String())
query.Set("timeout", r.timeout.String())
}
finalURL.RawQuery = query.Encode()
return finalURL.String()
@ -427,6 +458,14 @@ func (r *Request) Do() Result {
return Result{err: &RequestConstructionError{r.err}}
}
// TODO: added to catch programmer errors (invoking operations with an object with an empty namespace)
if (r.verb == "GET" || r.verb == "PUT" || r.verb == "DELETE") && r.namespaceSet && len(r.resourceName) > 0 && len(r.namespace) == 0 {
return Result{err: &RequestConstructionError{fmt.Errorf("an empty namespace may not be set when a resource name is provided")}}
}
if (r.verb == "POST") && r.namespaceSet && len(r.namespace) == 0 {
return Result{err: &RequestConstructionError{fmt.Errorf("an empty namespace may not be set during creation")}}
}
req, err := http.NewRequest(r.verb, r.finalURL(), r.body)
if err != nil {
return Result{err: &RequestConstructionError{err}}

View File

@ -148,7 +148,19 @@ func TestRequestParseSelectorParam(t *testing.T) {
func TestRequestParam(t *testing.T) {
r := (&Request{}).Param("foo", "a")
if !api.Semantic.DeepDerivative(r.params, map[string]string{"foo": "a"}) {
if !api.Semantic.DeepDerivative(r.params, url.Values{"foo": []string{"a"}}) {
t.Errorf("should have set a param: %#v", r)
}
}
func TestRequestURI(t *testing.T) {
r := (&Request{}).Param("foo", "a")
r.Prefix("other")
r.RequestURI("/test?foo=b&a=b")
if r.path != "/test" {
t.Errorf("path is wrong: %#v", r)
}
if !api.Semantic.DeepDerivative(r.params, url.Values{"a": []string{"b"}, "foo": []string{"b"}}) {
t.Errorf("should have set a param: %#v", r)
}
}

View File

@ -56,17 +56,15 @@ func TestCreateObjects(t *testing.T) {
items := []runtime.Object{}
items = append(items, &api.Pod{
TypeMeta: api.TypeMeta{APIVersion: "v1beta1", Kind: "Pod"},
ObjectMeta: api.ObjectMeta{Name: "test-pod"},
ObjectMeta: api.ObjectMeta{Name: "test-pod", Namespace: "default"},
})
items = append(items, &api.Service{
TypeMeta: api.TypeMeta{APIVersion: "v1beta1", Kind: "Service"},
ObjectMeta: api.ObjectMeta{Name: "test-service"},
ObjectMeta: api.ObjectMeta{Name: "test-service", Namespace: "default"},
})
typer, mapper := getTyperAndMapper()
client, s := getFakeClient(t, []string{"/api/v1beta1/pods", "/api/v1beta1/services"})
client, s := getFakeClient(t, []string{"/api/v1beta1/pods?namespace=default", "/api/v1beta1/services?namespace=default"})
errs := CreateObjects(typer, mapper, client, items)
s.Close()

View File

@ -35,6 +35,8 @@ type Helper struct {
// An interface for reading or writing the resource version of this
// type.
Versioner runtime.ResourceVersioner
// True if the resource type is scoped to namespaces
NamespaceScoped bool
}
// NewHelper creates a Helper from a ResourceMapping
@ -44,12 +46,14 @@ func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
Resource: mapping.Resource,
Codec: mapping.Codec,
Versioner: mapping.MetadataAccessor,
NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
}
}
func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
return m.RESTClient.Get().
Namespace(namespace).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
Do().
@ -58,7 +62,7 @@ func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
func (m *Helper) List(namespace string, selector labels.Selector) (runtime.Object, error) {
return m.RESTClient.Get().
Namespace(namespace).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
SelectorParam("labels", selector).
Do().
@ -68,7 +72,7 @@ func (m *Helper) List(namespace string, selector labels.Selector) (runtime.Objec
func (m *Helper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
return m.RESTClient.Get().
Prefix("watch").
Namespace(namespace).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Param("resourceVersion", resourceVersion).
SelectorParam("labels", labelSelector).
@ -79,7 +83,7 @@ func (m *Helper) Watch(namespace, resourceVersion string, labelSelector, fieldSe
func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
return m.RESTClient.Get().
Prefix("watch").
Namespace(namespace).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
Param("resourceVersion", resourceVersion).
@ -88,7 +92,7 @@ func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Int
func (m *Helper) Delete(namespace, name string) error {
return m.RESTClient.Delete().
Namespace(namespace).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
Do().
@ -100,14 +104,14 @@ func (m *Helper) Create(namespace string, modify bool, data []byte) (runtime.Obj
obj, err := m.Codec.Decode(data)
if err != nil {
// We don't know how to check a version on this object, but create it anyway
return createResource(m.RESTClient, m.Resource, namespace, data)
return m.createResource(m.RESTClient, m.Resource, namespace, data)
}
// Attempt to version the object based on client logic.
version, err := m.Versioner.ResourceVersion(obj)
if err != nil {
// We don't know how to clear the version on this object, so send it to the server as is
return createResource(m.RESTClient, m.Resource, namespace, data)
return m.createResource(m.RESTClient, m.Resource, namespace, data)
}
if version != "" {
if err := m.Versioner.SetResourceVersion(obj, ""); err != nil {
@ -121,11 +125,11 @@ func (m *Helper) Create(namespace string, modify bool, data []byte) (runtime.Obj
}
}
return createResource(m.RESTClient, m.Resource, namespace, data)
return m.createResource(m.RESTClient, m.Resource, namespace, data)
}
func createResource(c RESTClient, resource, namespace string, data []byte) (runtime.Object, error) {
return c.Post().Namespace(namespace).Resource(resource).Body(data).Do().Get()
func (m *Helper) createResource(c RESTClient, resource, namespace string, data []byte) (runtime.Object, error) {
return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(data).Do().Get()
}
func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) (runtime.Object, error) {
@ -134,21 +138,21 @@ func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) (ru
obj, err := m.Codec.Decode(data)
if err != nil {
// We don't know how to handle this object, but update it anyway
return updateResource(c, m.Resource, namespace, name, data)
return m.updateResource(c, m.Resource, namespace, name, data)
}
// Attempt to version the object based on client logic.
version, err := m.Versioner.ResourceVersion(obj)
if err != nil {
// We don't know how to version this object, so send it to the server as is
return updateResource(c, m.Resource, namespace, name, data)
return m.updateResource(c, m.Resource, namespace, name, data)
}
if version == "" && overwrite {
// Retrieve the current version of the object to overwrite the server object
serverObj, err := c.Get().Namespace(namespace).Resource(m.Resource).Name(name).Do().Get()
if err != nil {
// The object does not exist, but we want it to be created
return updateResource(c, m.Resource, namespace, name, data)
return m.updateResource(c, m.Resource, namespace, name, data)
}
serverVersion, err := m.Versioner.ResourceVersion(serverObj)
if err != nil {
@ -164,9 +168,9 @@ func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) (ru
data = newData
}
return updateResource(c, m.Resource, namespace, name, data)
return m.updateResource(c, m.Resource, namespace, name, data)
}
func updateResource(c RESTClient, resource, namespace, name string, data []byte) (runtime.Object, error) {
return c.Put().Namespace(namespace).Resource(resource).Name(name).Body(data).Do().Get()
func (m *Helper) updateResource(c RESTClient, resource, namespace, name string, data []byte) (runtime.Object, error) {
return c.Put().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Name(name).Body(data).Do().Get()
}

View File

@ -75,6 +75,10 @@ func TestHelperDelete(t *testing.T) {
return false
}
parts := splitPath(req.URL.Path)
if len(parts) < 3 {
t.Errorf("expected URL path to have 3 parts: %s", req.URL.Path)
return false
}
if parts[1] != "bar" {
t.Errorf("url doesn't contain namespace: %#v", req)
return false
@ -94,7 +98,8 @@ func TestHelperDelete(t *testing.T) {
Err: test.HttpErr,
}
modifier := &Helper{
RESTClient: client,
RESTClient: client,
NamespaceScoped: true,
}
err := modifier.Delete("bar", "foo")
if (err != nil) != test.Err {
@ -185,9 +190,10 @@ func TestHelperCreate(t *testing.T) {
client.Client = test.RespFunc
}
modifier := &Helper{
RESTClient: client,
Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(),
RESTClient: client,
Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(),
NamespaceScoped: true,
}
data := []byte{}
if test.Object != nil {
@ -267,7 +273,8 @@ func TestHelperGet(t *testing.T) {
Err: test.HttpErr,
}
modifier := &Helper{
RESTClient: client,
RESTClient: client,
NamespaceScoped: true,
}
obj, err := modifier.Get("bar", "foo")
if (err != nil) != test.Err {
@ -337,7 +344,8 @@ func TestHelperList(t *testing.T) {
Err: test.HttpErr,
}
modifier := &Helper{
RESTClient: client,
RESTClient: client,
NamespaceScoped: true,
}
obj, err := modifier.List("bar", labels.SelectorFromSet(labels.Set{"foo": "baz"}))
if (err != nil) != test.Err {
@ -440,9 +448,10 @@ func TestHelperUpdate(t *testing.T) {
client.Client = test.RespFunc
}
modifier := &Helper{
RESTClient: client,
Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(),
RESTClient: client,
Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(),
NamespaceScoped: true,
}
data := []byte{}
if test.Object != nil {

View File

@ -260,7 +260,7 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) {
serviceList := api.ServiceList{
Items: []api.Service{
{
ObjectMeta: api.ObjectMeta{Name: "foo"},
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "other"},
Spec: api.ServiceSpec{
Selector: map[string]string{},
},
@ -290,14 +290,14 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) {
},
Endpoints: []string{"1.2.3.4:8080"},
})
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "PUT", &data)
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo?namespace=other", "PUT", &data)
}
func TestSyncEndpointsItemsPreexisting(t *testing.T) {
serviceList := api.ServiceList{
Items: []api.Service{
{
ObjectMeta: api.ObjectMeta{Name: "foo"},
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"},
Spec: api.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
@ -329,14 +329,14 @@ func TestSyncEndpointsItemsPreexisting(t *testing.T) {
},
Endpoints: []string{"1.2.3.4:8080"},
})
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "PUT", &data)
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo?namespace=bar", "PUT", &data)
}
func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
serviceList := api.ServiceList{
Items: []api.Service{
{
ObjectMeta: api.ObjectMeta{Name: "foo"},
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
@ -360,14 +360,14 @@ func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
if err := endpoints.SyncServiceEndpoints(); err != nil {
t.Errorf("unexpected error: %v", err)
}
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "GET", nil)
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo?namespace=default", "GET", nil)
}
func TestSyncEndpointsItems(t *testing.T) {
serviceList := api.ServiceList{
Items: []api.Service{
{
ObjectMeta: api.ObjectMeta{Name: "foo"},
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "other"},
Spec: api.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
@ -392,7 +392,7 @@ func TestSyncEndpointsItems(t *testing.T) {
},
Endpoints: []string{"1.2.3.4:8080"},
})
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints", "POST", &data)
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints?namespace=other", "POST", &data)
}
func TestSyncEndpointsPodError(t *testing.T) {

View File

@ -277,7 +277,13 @@ func TestBind(t *testing.T) {
table := []struct {
binding *api.Binding
}{
{binding: &api.Binding{PodID: "foo", Host: "foohost.kubernetes.mydomain.com"}},
{binding: &api.Binding{
ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceDefault,
},
PodID: "foo",
Host: "foohost.kubernetes.mydomain.com",
}},
}
for _, item := range table {
@ -296,7 +302,7 @@ func TestBind(t *testing.T) {
continue
}
expectedBody := runtime.EncodeOrDie(testapi.Codec(), item.binding)
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/bindings", "POST", &expectedBody)
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/bindings?namespace=default", "POST", &expectedBody)
}
}