mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #4395 from smarterclayton/split_naming
Fix cross-namespace LIST and WATCH
This commit is contained in:
commit
876d651737
@ -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))
|
||||
|
@ -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[@]}"
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
8
pkg/client/cache/listwatch_test.go
vendored
8
pkg/client/cache/listwatch_test.go
vendored
@ -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
|
||||
}
|
||||
|
@ -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).
|
||||
|
@ -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) {
|
||||
|
@ -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}}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user