mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 02:09:56 +00:00
Move namespace from query param to path part
This commit is contained in:
parent
58ba3c7faa
commit
7cf664439f
@ -222,6 +222,7 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, root string,
|
|||||||
|
|
||||||
for path, storage := range g.handler.storage {
|
for path, storage := range g.handler.storage {
|
||||||
registerResourceHandlers(ws, version, path, storage, kinds, h)
|
registerResourceHandlers(ws, version, path, storage, kinds, h)
|
||||||
|
registerResourceHandlers(ws, version, "ns/{namespace}/"+path, storage, kinds, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: port the rest of these. Sadly, if we don't, we'll have inconsistent
|
// TODO: port the rest of these. Sadly, if we don't, we'll have inconsistent
|
||||||
|
@ -683,7 +683,7 @@ func TestSyncCreate(t *testing.T) {
|
|||||||
t: t,
|
t: t,
|
||||||
name: "bar",
|
name: "bar",
|
||||||
namespace: "other",
|
namespace: "other",
|
||||||
expectedSet: "/prefix/version/foo/bar?namespace=other",
|
expectedSet: "/prefix/version/ns/other/foo/bar",
|
||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": &storage,
|
"foo": &storage,
|
||||||
@ -696,7 +696,7 @@ func TestSyncCreate(t *testing.T) {
|
|||||||
Other: "bar",
|
Other: "bar",
|
||||||
}
|
}
|
||||||
data, _ := codec.Encode(simple)
|
data, _ := codec.Encode(simple)
|
||||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?sync=true", bytes.NewBuffer(data))
|
request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo?sync=true", bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
||||||
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
|
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||||
@ -40,24 +41,6 @@ var specialVerbs = map[string]bool{
|
|||||||
"watch": true,
|
"watch": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindFromRequest returns Kind if Kind can be extracted from the request. Otherwise, the empty string.
|
|
||||||
func KindFromRequest(req http.Request) string {
|
|
||||||
// TODO: find a way to keep this code's assumptions about paths up to date with changes in the code. Maybe instead
|
|
||||||
// of directly adding handler's code to the master's Mux, have a function which forces the structure when adding
|
|
||||||
// them.
|
|
||||||
parts := splitPath(req.URL.Path)
|
|
||||||
if len(parts) > 2 && parts[0] == "api" {
|
|
||||||
if _, ok := specialVerbs[parts[2]]; ok {
|
|
||||||
if len(parts) > 3 {
|
|
||||||
return parts[3]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return parts[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReadOnlyReq() is true for any (or at least many) request which has no observable
|
// IsReadOnlyReq() is true for any (or at least many) request which has no observable
|
||||||
// side effects on state of apiserver (though there may be internal side effects like
|
// side effects on state of apiserver (though there may be internal side effects like
|
||||||
// caching and logging).
|
// caching and logging).
|
||||||
@ -185,14 +168,16 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
|||||||
|
|
||||||
attribs.ReadOnly = IsReadOnlyReq(*req)
|
attribs.ReadOnly = IsReadOnlyReq(*req)
|
||||||
|
|
||||||
|
namespace, kind, _, _ := KindAndNamespace(req)
|
||||||
|
|
||||||
// If a path follows the conventions of the REST object store, then
|
// If a path follows the conventions of the REST object store, then
|
||||||
// we can extract the object Kind. Otherwise, not.
|
// we can extract the object Kind. Otherwise, not.
|
||||||
attribs.Kind = KindFromRequest(*req)
|
attribs.Kind = kind
|
||||||
|
|
||||||
// If the request specifies a namespace, then the namespace is filled in.
|
// If the request specifies a namespace, then the namespace is filled in.
|
||||||
// Assumes there is no empty string namespace. Unspecified results
|
// Assumes there is no empty string namespace. Unspecified results
|
||||||
// in empty (does not understand defaulting rules.)
|
// in empty (does not understand defaulting rules.)
|
||||||
attribs.Namespace = req.URL.Query().Get("namespace")
|
attribs.Namespace = namespace
|
||||||
|
|
||||||
return &attribs
|
return &attribs
|
||||||
}
|
}
|
||||||
@ -208,3 +193,79 @@ func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGet
|
|||||||
forbidden(w, req)
|
forbidden(w, req)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KindAndNamespace returns the kind, namespace, and path parts for the request relative to /{kind}/{name}
|
||||||
|
// Valid Inputs:
|
||||||
|
// Storage paths
|
||||||
|
// /ns/{namespace}/{kind}
|
||||||
|
// /ns/{namespace}/{kind}/{resourceName}
|
||||||
|
// /{kind}
|
||||||
|
// /{kind}/{resourceName}
|
||||||
|
// /{kind}/{resourceName}?namespace={namespace}
|
||||||
|
// /{kind}?namespace={namespace}
|
||||||
|
//
|
||||||
|
// Special verbs:
|
||||||
|
// /proxy/{kind}/{resourceName}
|
||||||
|
// /proxy/ns/{namespace}/{kind}/{resourceName}
|
||||||
|
// /redirect/ns/{namespace}/{kind}/{resourceName}
|
||||||
|
// /redirect/{kind}/{resourceName}
|
||||||
|
// /watch/{kind}
|
||||||
|
// /watch/ns/{namespace}/{kind}
|
||||||
|
//
|
||||||
|
// Fully qualified paths for above:
|
||||||
|
// /api/{version}/*
|
||||||
|
// /api/{version}/*
|
||||||
|
func KindAndNamespace(req *http.Request) (namespace, kind string, parts []string, err error) {
|
||||||
|
parts = splitPath(req.URL.Path)
|
||||||
|
if len(parts) < 1 {
|
||||||
|
err = fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle input of form /api/{version}/* by adjusting special paths
|
||||||
|
if parts[0] == "api" {
|
||||||
|
if len(parts) > 2 {
|
||||||
|
parts = parts[2:]
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle input of form /{specialVerb}/*
|
||||||
|
if _, ok := specialVerbs[parts[0]]; ok {
|
||||||
|
if len(parts) > 1 {
|
||||||
|
parts = parts[1:]
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL forms: /ns/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||||
|
if parts[0] == "ns" {
|
||||||
|
if len(parts) < 3 {
|
||||||
|
err = fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
namespace = parts[1]
|
||||||
|
kind = parts[2]
|
||||||
|
parts = parts[2:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL forms: /{kind}/*
|
||||||
|
// URL forms: POST /{kind} is a legacy API convention to create in "default" namespace
|
||||||
|
// URL forms: /{kind}/{resourceName} use the "default" namespace if omitted from query param
|
||||||
|
// URL forms: /{kind} assume cross-namespace operation if omitted from query param
|
||||||
|
kind = parts[0]
|
||||||
|
namespace = req.URL.Query().Get("namespace")
|
||||||
|
if len(namespace) == 0 {
|
||||||
|
if len(parts) > 1 || req.Method == "POST" {
|
||||||
|
namespace = api.NamespaceDefault
|
||||||
|
} else {
|
||||||
|
namespace = api.NamespaceAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -19,7 +19,10 @@ package apiserver
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeRL bool
|
type fakeRL bool
|
||||||
@ -59,3 +62,75 @@ func TestReadOnly(t *testing.T) {
|
|||||||
http.DefaultClient.Do(req)
|
http.DefaultClient.Do(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKindAndNamespace(t *testing.T) {
|
||||||
|
successCases := []struct {
|
||||||
|
url string
|
||||||
|
expectedNamespace string
|
||||||
|
expectedKind string
|
||||||
|
expectedParts []string
|
||||||
|
}{
|
||||||
|
// resource paths
|
||||||
|
{"/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||||
|
{"/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||||
|
{"/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||||
|
{"/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||||
|
{"/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
|
||||||
|
{"/pods?namespace=other", "other", "pods", []string{"pods"}},
|
||||||
|
|
||||||
|
// special verbs
|
||||||
|
{"/proxy/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||||
|
{"/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||||
|
{"/redirect/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||||
|
{"/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||||
|
{"/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||||
|
{"/watch/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||||
|
|
||||||
|
// full-qualified paths
|
||||||
|
{"/api/v1beta1/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||||
|
{"/api/v1beta1/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||||
|
{"/api/v1beta1/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||||
|
{"/api/v1beta1/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||||
|
{"/api/v1beta1/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
|
||||||
|
{"/api/v1beta1/pods?namespace=other", "other", "pods", []string{"pods"}},
|
||||||
|
{"/api/v1beta1/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||||
|
{"/api/v1beta1/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||||
|
{"/api/v1beta1/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||||
|
{"/api/v1beta1/watch/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, successCase := range successCases {
|
||||||
|
req, _ := http.NewRequest("GET", successCase.url, nil)
|
||||||
|
namespace, kind, parts, err := KindAndNamespace(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for url: %s", successCase.url)
|
||||||
|
}
|
||||||
|
if successCase.expectedNamespace != namespace {
|
||||||
|
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, namespace)
|
||||||
|
}
|
||||||
|
if successCase.expectedKind != kind {
|
||||||
|
t.Errorf("Unexpected resourceType for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, kind)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(successCase.expectedParts, parts) {
|
||||||
|
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, parts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCases := map[string]string{
|
||||||
|
"no resource path": "/",
|
||||||
|
"missing resource type": "/ns/other",
|
||||||
|
"just apiversion": "/api/v1beta1/",
|
||||||
|
"apiversion with no resource": "/api/v1beta1/",
|
||||||
|
"apiversion with just namespace": "/api/v1beta1/ns/other",
|
||||||
|
}
|
||||||
|
for k, v := range errorCases {
|
||||||
|
req, err := http.NewRequest("GET", v, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
_, _, _, err = KindAndNamespace(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error for key: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -78,35 +78,31 @@ type ProxyHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
// use the default namespace to address the service
|
namespace, kind, parts, err := KindAndNamespace(req)
|
||||||
ctx := api.NewDefaultContext()
|
if err != nil {
|
||||||
// if not in default namespace, provide the query parameter
|
notFound(w, req)
|
||||||
// TODO this will need to go in the path in the future and not as a query parameter
|
return
|
||||||
namespace := req.URL.Query().Get("namespace")
|
|
||||||
if len(namespace) > 0 {
|
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
|
||||||
}
|
}
|
||||||
parts := strings.SplitN(req.URL.Path, "/", 3)
|
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resourceName := parts[0]
|
|
||||||
id := parts[1]
|
id := parts[1]
|
||||||
rest := ""
|
rest := ""
|
||||||
if len(parts) == 3 {
|
if len(parts) == 3 {
|
||||||
rest = parts[2]
|
rest = parts[2]
|
||||||
}
|
}
|
||||||
storage, ok := r.storage[resourceName]
|
storage, ok := r.storage[kind]
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resourceName)
|
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirector, ok := storage.(Redirector)
|
redirector, ok := storage.(Redirector)
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resourceName)
|
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -150,7 +146,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
proxy.Transport = &proxyTransport{
|
proxy.Transport = &proxyTransport{
|
||||||
proxyScheme: req.URL.Scheme,
|
proxyScheme: req.URL.Scheme,
|
||||||
proxyHost: req.URL.Host,
|
proxyHost: req.URL.Host,
|
||||||
proxyPathPrepend: path.Join(r.prefix, resourceName, id),
|
proxyPathPrepend: path.Join(r.prefix, "ns", namespace, kind, id),
|
||||||
}
|
}
|
||||||
proxy.FlushInterval = 200 * time.Millisecond
|
proxy.FlushInterval = 200 * time.Millisecond
|
||||||
proxy.ServeHTTP(w, newReq)
|
proxy.ServeHTTP(w, newReq)
|
||||||
|
@ -142,7 +142,7 @@ func TestProxy(t *testing.T) {
|
|||||||
{"POST", "/some/other/dir", "question", "answer", "default"},
|
{"POST", "/some/other/dir", "question", "answer", "default"},
|
||||||
{"PUT", "/some/dir/id", "different question", "answer", "default"},
|
{"PUT", "/some/dir/id", "different question", "answer", "default"},
|
||||||
{"DELETE", "/some/dir/id", "", "ok", "default"},
|
{"DELETE", "/some/dir/id", "", "ok", "default"},
|
||||||
{"GET", "/some/dir/id?namespace=other", "", "answer", "other"},
|
{"GET", "/some/dir/id", "", "answer", "other"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
@ -169,9 +169,15 @@ func TestProxy(t *testing.T) {
|
|||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
// test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace
|
||||||
|
proxyTestPatterns := []string{
|
||||||
|
"/prefix/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace,
|
||||||
|
"/prefix/version/proxy/ns/" + item.reqNamespace + "/foo/id" + item.path,
|
||||||
|
}
|
||||||
|
for _, proxyTestPattern := range proxyTestPatterns {
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
item.method,
|
item.method,
|
||||||
server.URL+"/prefix/version/proxy/foo/id"+item.path,
|
server.URL+proxyTestPattern,
|
||||||
strings.NewReader(item.reqBody),
|
strings.NewReader(item.reqBody),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -192,4 +198,5 @@ func TestProxy(t *testing.T) {
|
|||||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,28 +30,29 @@ type RedirectHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := api.NewDefaultContext()
|
namespace, kind, parts, err := KindAndNamespace(req)
|
||||||
namespace := req.URL.Query().Get("namespace")
|
if err != nil {
|
||||||
if len(namespace) > 0 {
|
notFound(w, req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
return
|
||||||
}
|
}
|
||||||
parts := splitPath(req.URL.Path)
|
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||||
|
|
||||||
|
// redirection requires /kind/resourceName path parts
|
||||||
if len(parts) != 2 || req.Method != "GET" {
|
if len(parts) != 2 || req.Method != "GET" {
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resourceName := parts[0]
|
|
||||||
id := parts[1]
|
id := parts[1]
|
||||||
storage, ok := r.storage[resourceName]
|
storage, ok := r.storage[kind]
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resourceName)
|
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirector, ok := storage.(Redirector)
|
redirector, ok := storage.(Redirector)
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resourceName)
|
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -76,3 +76,56 @@ func TestRedirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedirectWithNamespaces(t *testing.T) {
|
||||||
|
simpleStorage := &SimpleRESTStorage{
|
||||||
|
errors: map[string]error{},
|
||||||
|
expectedResourceNamespace: "other",
|
||||||
|
}
|
||||||
|
handler := Handle(map[string]RESTStorage{
|
||||||
|
"foo": simpleStorage,
|
||||||
|
}, codec, "/prefix", "version", selfLinker)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
dontFollow := errors.New("don't follow")
|
||||||
|
client := http.Client{
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return dontFollow
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
id string
|
||||||
|
err error
|
||||||
|
code int
|
||||||
|
}{
|
||||||
|
{"cozy", nil, http.StatusTemporaryRedirect},
|
||||||
|
{"horse", errors.New("no such id"), http.StatusInternalServerError},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
simpleStorage.errors["resourceLocation"] = item.err
|
||||||
|
simpleStorage.resourceLocation = item.id
|
||||||
|
resp, err := client.Get(server.URL + "/prefix/version/redirect/ns/other/foo/" + item.id)
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("Unexpected nil resp")
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
if e, a := item.code, resp.StatusCode; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
if e, a := item.id, simpleStorage.requestedResourceLocationID; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
if item.err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil || err.(*url.Error).Err != dontFollow {
|
||||||
|
t.Errorf("Unexpected err %#v", err)
|
||||||
|
}
|
||||||
|
if e, a := item.id, resp.Header.Get("Location"); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,19 +41,19 @@ type RESTHandler struct {
|
|||||||
|
|
||||||
// ServeHTTP handles requests to all RESTStorage objects.
|
// ServeHTTP handles requests to all RESTStorage objects.
|
||||||
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
parts := splitPath(req.URL.Path)
|
namespace, kind, parts, err := KindAndNamespace(req)
|
||||||
if len(parts) < 1 {
|
if err != nil {
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
storage := h.storage[parts[0]]
|
storage := h.storage[kind]
|
||||||
if storage == nil {
|
if storage == nil {
|
||||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", parts[0])
|
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.handleRESTStorage(parts, req, w, storage)
|
h.handleRESTStorage(parts, req, w, storage, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the SelfLink field of the object.
|
// Sets the SelfLink field of the object.
|
||||||
@ -66,12 +66,17 @@ func (h *RESTHandler) setSelfLink(obj runtime.Object, req *http.Request) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TODO Remove this when namespace is in path
|
|
||||||
|
// we need to add namespace as a query param, if its not in the resource path
|
||||||
if len(namespace) > 0 {
|
if len(namespace) > 0 {
|
||||||
|
parts := splitPath(req.URL.Path)
|
||||||
|
if parts[0] != "ns" {
|
||||||
query := newURL.Query()
|
query := newURL.Query()
|
||||||
query.Set("namespace", namespace)
|
query.Set("namespace", namespace)
|
||||||
newURL.RawQuery = query.Encode()
|
newURL.RawQuery = query.Encode()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = h.selfLinker.SetSelfLink(obj, newURL.String())
|
err = h.selfLinker.SetSelfLink(obj, newURL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -107,12 +112,15 @@ func (h *RESTHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request)
|
|||||||
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
|
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
|
||||||
newURL.RawQuery = ""
|
newURL.RawQuery = ""
|
||||||
newURL.Fragment = ""
|
newURL.Fragment = ""
|
||||||
// TODO Remove this when namespace is in path
|
// we need to add namespace as a query param, if its not in the resource path
|
||||||
if len(namespace) > 0 {
|
if len(namespace) > 0 {
|
||||||
|
parts := splitPath(req.URL.Path)
|
||||||
|
if parts[0] != "ns" {
|
||||||
query := newURL.Query()
|
query := newURL.Query()
|
||||||
query.Set("namespace", namespace)
|
query.Set("namespace", namespace)
|
||||||
newURL.RawQuery = query.Encode()
|
newURL.RawQuery = query.Encode()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return h.selfLinker.SetSelfLink(obj, newURL.String())
|
return h.selfLinker.SetSelfLink(obj, newURL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,18 +146,10 @@ func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(
|
|||||||
// sync=[false|true] Synchronous request (only applies to create, update, delete operations)
|
// sync=[false|true] Synchronous request (only applies to create, update, delete operations)
|
||||||
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
|
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
|
||||||
// labels=<label-selector> Used for filtering list operations
|
// labels=<label-selector> Used for filtering list operations
|
||||||
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
|
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage, namespace string) {
|
||||||
ctx := api.NewContext()
|
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||||
sync := req.URL.Query().Get("sync") == "true"
|
sync := req.URL.Query().Get("sync") == "true"
|
||||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||||
// TODO for now, we pull namespace from query parameter, but according to spec, it must go in resource path in future PR
|
|
||||||
// if a namespace if specified, it's always used.
|
|
||||||
// for list/watch operations, a namespace is not required if omitted.
|
|
||||||
// for all other operations, if namespace is omitted, we will default to default namespace.
|
|
||||||
namespace := req.URL.Query().Get("namespace")
|
|
||||||
if len(namespace) > 0 {
|
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
|
||||||
}
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
@ -175,7 +175,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
|||||||
}
|
}
|
||||||
writeJSON(http.StatusOK, h.codec, list, w)
|
writeJSON(http.StatusOK, h.codec, list, w)
|
||||||
case 2:
|
case 2:
|
||||||
item, err := storage.Get(api.WithNamespaceDefaultIfNone(ctx), parts[1])
|
item, err := storage.Get(ctx, parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, h.codec, w)
|
errorJSON(err, h.codec, w)
|
||||||
return
|
return
|
||||||
@ -205,7 +205,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
|||||||
errorJSON(err, h.codec, w)
|
errorJSON(err, h.codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out, err := storage.Create(api.WithNamespaceDefaultIfNone(ctx), obj)
|
out, err := storage.Create(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, h.codec, w)
|
errorJSON(err, h.codec, w)
|
||||||
return
|
return
|
||||||
@ -218,7 +218,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
|||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out, err := storage.Delete(api.WithNamespaceDefaultIfNone(ctx), parts[1])
|
out, err := storage.Delete(ctx, parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, h.codec, w)
|
errorJSON(err, h.codec, w)
|
||||||
return
|
return
|
||||||
@ -242,7 +242,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
|||||||
errorJSON(err, h.codec, w)
|
errorJSON(err, h.codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out, err := storage.Update(api.WithNamespaceDefaultIfNone(ctx), obj)
|
out, err := storage.Update(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, h.codec, w)
|
errorJSON(err, h.codec, w)
|
||||||
return
|
return
|
||||||
|
@ -77,17 +77,19 @@ func isWebsocketRequest(req *http.Request) bool {
|
|||||||
|
|
||||||
// ServeHTTP processes watch requests.
|
// ServeHTTP processes watch requests.
|
||||||
func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := api.NewContext()
|
if req.Method != "GET" {
|
||||||
namespace := req.URL.Query().Get("namespace")
|
|
||||||
if len(namespace) > 0 {
|
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
|
||||||
}
|
|
||||||
parts := splitPath(req.URL.Path)
|
|
||||||
if len(parts) < 1 || req.Method != "GET" {
|
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
storage := h.storage[parts[0]]
|
|
||||||
|
namespace, kind, _, err := KindAndNamespace(req)
|
||||||
|
if err != nil {
|
||||||
|
notFound(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||||
|
|
||||||
|
storage := h.storage[kind]
|
||||||
if storage == nil {
|
if storage == nil {
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
|
@ -161,10 +161,18 @@ func (c *testClient) ValidateCommon(t *testing.T, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convenience function to build paths
|
||||||
|
func buildResourcePath(namespace, resource string) string {
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
return path.Join("ns", namespace, resource)
|
||||||
|
}
|
||||||
|
return resource
|
||||||
|
}
|
||||||
|
|
||||||
func TestListEmptyPods(t *testing.T) {
|
func TestListEmptyPods(t *testing.T) {
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/pods"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods")},
|
||||||
Response: Response{StatusCode: 200, Body: &api.PodList{}},
|
Response: Response{StatusCode: 200, Body: &api.PodList{}},
|
||||||
}
|
}
|
||||||
podList, err := c.Setup().Pods(ns).List(labels.Everything())
|
podList, err := c.Setup().Pods(ns).List(labels.Everything())
|
||||||
@ -174,7 +182,7 @@ func TestListEmptyPods(t *testing.T) {
|
|||||||
func TestListPods(t *testing.T) {
|
func TestListPods(t *testing.T) {
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/pods"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods")},
|
||||||
Response: Response{StatusCode: 200,
|
Response: Response{StatusCode: 200,
|
||||||
Body: &api.PodList{
|
Body: &api.PodList{
|
||||||
Items: []api.Pod{
|
Items: []api.Pod{
|
||||||
@ -206,7 +214,7 @@ func validateLabels(a, b string) bool {
|
|||||||
func TestListPodsLabels(t *testing.T) {
|
func TestListPodsLabels(t *testing.T) {
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/pods", Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods"), Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
||||||
Response: Response{
|
Response: Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: &api.PodList{
|
Body: &api.PodList{
|
||||||
@ -236,7 +244,7 @@ func TestListPodsLabels(t *testing.T) {
|
|||||||
func TestGetPod(t *testing.T) {
|
func TestGetPod(t *testing.T) {
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/pods/foo"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods/foo")},
|
||||||
Response: Response{
|
Response: Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: &api.Pod{
|
Body: &api.Pod{
|
||||||
@ -268,15 +276,17 @@ func TestGetPodWithNoName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeletePod(t *testing.T) {
|
func TestDeletePod(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "DELETE", Path: "/pods/foo"},
|
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/pods/foo")},
|
||||||
Response: Response{StatusCode: 200},
|
Response: Response{StatusCode: 200},
|
||||||
}
|
}
|
||||||
err := c.Setup().Pods(api.NamespaceDefault).Delete("foo")
|
err := c.Setup().Pods(ns).Delete("foo")
|
||||||
c.Validate(t, nil, err)
|
c.Validate(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatePod(t *testing.T) {
|
func TestCreatePod(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
requestPod := &api.Pod{
|
requestPod := &api.Pod{
|
||||||
Status: api.PodStatus{
|
Status: api.PodStatus{
|
||||||
Phase: api.PodRunning,
|
Phase: api.PodRunning,
|
||||||
@ -289,17 +299,18 @@ func TestCreatePod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "POST", Path: "/pods", Body: requestPod},
|
Request: testRequest{Method: "POST", Path: buildResourcePath(ns, "/pods"), Body: requestPod},
|
||||||
Response: Response{
|
Response: Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: requestPod,
|
Body: requestPod,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receivedPod, err := c.Setup().Pods(api.NamespaceDefault).Create(requestPod)
|
receivedPod, err := c.Setup().Pods(ns).Create(requestPod)
|
||||||
c.Validate(t, receivedPod, err)
|
c.Validate(t, receivedPod, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdatePod(t *testing.T) {
|
func TestUpdatePod(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
requestPod := &api.Pod{
|
requestPod := &api.Pod{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
@ -314,10 +325,10 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "PUT", Path: "/pods/foo"},
|
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/pods/foo")},
|
||||||
Response: Response{StatusCode: 200, Body: requestPod},
|
Response: Response{StatusCode: 200, Body: requestPod},
|
||||||
}
|
}
|
||||||
receivedPod, err := c.Setup().Pods(api.NamespaceDefault).Update(requestPod)
|
receivedPod, err := c.Setup().Pods(ns).Update(requestPod)
|
||||||
c.Validate(t, receivedPod, err)
|
c.Validate(t, receivedPod, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,8 +361,9 @@ func TestListControllers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetController(t *testing.T) {
|
func TestGetController(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/replicationControllers/foo"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/replicationControllers/foo")},
|
||||||
Response: Response{
|
Response: Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: &api.ReplicationController{
|
Body: &api.ReplicationController{
|
||||||
@ -369,7 +381,7 @@ func TestGetController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receivedController, err := c.Setup().ReplicationControllers(api.NamespaceDefault).Get("foo")
|
receivedController, err := c.Setup().ReplicationControllers(ns).Get("foo")
|
||||||
c.Validate(t, receivedController, err)
|
c.Validate(t, receivedController, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,11 +397,12 @@ func TestGetControllerWithNoName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateController(t *testing.T) {
|
func TestUpdateController(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
requestController := &api.ReplicationController{
|
requestController := &api.ReplicationController{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
|
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
|
||||||
}
|
}
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "PUT", Path: "/replicationControllers/foo"},
|
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/replicationControllers/foo")},
|
||||||
Response: Response{
|
Response: Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: &api.ReplicationController{
|
Body: &api.ReplicationController{
|
||||||
@ -407,25 +420,27 @@ func TestUpdateController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receivedController, err := c.Setup().ReplicationControllers(api.NamespaceDefault).Update(requestController)
|
receivedController, err := c.Setup().ReplicationControllers(ns).Update(requestController)
|
||||||
c.Validate(t, receivedController, err)
|
c.Validate(t, receivedController, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteController(t *testing.T) {
|
func TestDeleteController(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "DELETE", Path: "/replicationControllers/foo"},
|
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/replicationControllers/foo")},
|
||||||
Response: Response{StatusCode: 200},
|
Response: Response{StatusCode: 200},
|
||||||
}
|
}
|
||||||
err := c.Setup().ReplicationControllers(api.NamespaceDefault).Delete("foo")
|
err := c.Setup().ReplicationControllers(ns).Delete("foo")
|
||||||
c.Validate(t, nil, err)
|
c.Validate(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateController(t *testing.T) {
|
func TestCreateController(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
requestController := &api.ReplicationController{
|
requestController := &api.ReplicationController{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
}
|
}
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "POST", Path: "/replicationControllers", Body: requestController},
|
Request: testRequest{Method: "POST", Path: buildResourcePath(ns, "/replicationControllers"), Body: requestController},
|
||||||
Response: Response{
|
Response: Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: &api.ReplicationController{
|
Body: &api.ReplicationController{
|
||||||
@ -443,7 +458,7 @@ func TestCreateController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receivedController, err := c.Setup().ReplicationControllers(api.NamespaceDefault).Create(requestController)
|
receivedController, err := c.Setup().ReplicationControllers(ns).Create(requestController)
|
||||||
c.Validate(t, receivedController, err)
|
c.Validate(t, receivedController, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,8 +472,9 @@ func body(obj runtime.Object, raw *string) *string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListServices(t *testing.T) {
|
func TestListServices(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/services"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/services")},
|
||||||
Response: Response{StatusCode: 200,
|
Response: Response{StatusCode: 200,
|
||||||
Body: &api.ServiceList{
|
Body: &api.ServiceList{
|
||||||
Items: []api.Service{
|
Items: []api.Service{
|
||||||
@ -480,14 +496,15 @@ func TestListServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receivedServiceList, err := c.Setup().Services(api.NamespaceDefault).List(labels.Everything())
|
receivedServiceList, err := c.Setup().Services(ns).List(labels.Everything())
|
||||||
t.Logf("received services: %v %#v", err, receivedServiceList)
|
t.Logf("received services: %v %#v", err, receivedServiceList)
|
||||||
c.Validate(t, receivedServiceList, err)
|
c.Validate(t, receivedServiceList, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListServicesLabels(t *testing.T) {
|
func TestListServicesLabels(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/services", Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/services"), Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
||||||
Response: Response{StatusCode: 200,
|
Response: Response{StatusCode: 200,
|
||||||
Body: &api.ServiceList{
|
Body: &api.ServiceList{
|
||||||
Items: []api.Service{
|
Items: []api.Service{
|
||||||
@ -512,16 +529,17 @@ func TestListServicesLabels(t *testing.T) {
|
|||||||
c.Setup()
|
c.Setup()
|
||||||
c.QueryValidator["labels"] = validateLabels
|
c.QueryValidator["labels"] = validateLabels
|
||||||
selector := labels.Set{"foo": "bar", "name": "baz"}.AsSelector()
|
selector := labels.Set{"foo": "bar", "name": "baz"}.AsSelector()
|
||||||
receivedServiceList, err := c.Services(api.NamespaceDefault).List(selector)
|
receivedServiceList, err := c.Services(ns).List(selector)
|
||||||
c.Validate(t, receivedServiceList, err)
|
c.Validate(t, receivedServiceList, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetService(t *testing.T) {
|
func TestGetService(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/services/1"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/services/1")},
|
||||||
Response: Response{StatusCode: 200, Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
Response: Response{StatusCode: 200, Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||||
}
|
}
|
||||||
response, err := c.Setup().Services(api.NamespaceDefault).Get("1")
|
response, err := c.Setup().Services(ns).Get("1")
|
||||||
c.Validate(t, response, err)
|
c.Validate(t, response, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,36 +555,40 @@ func TestGetServiceWithNoName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateService(t *testing.T) {
|
func TestCreateService(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "POST", Path: "/services", Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
Request: testRequest{Method: "POST", Path: buildResourcePath(ns, "/services"), Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||||
Response: Response{StatusCode: 200, Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
Response: Response{StatusCode: 200, Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||||
}
|
}
|
||||||
response, err := c.Setup().Services(api.NamespaceDefault).Create(&api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}})
|
response, err := c.Setup().Services(ns).Create(&api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}})
|
||||||
c.Validate(t, response, err)
|
c.Validate(t, response, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateService(t *testing.T) {
|
func TestUpdateService(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
svc := &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1", ResourceVersion: "1"}}
|
svc := &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1", ResourceVersion: "1"}}
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "PUT", Path: "/services/service-1", Body: svc},
|
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/services/service-1"), Body: svc},
|
||||||
Response: Response{StatusCode: 200, Body: svc},
|
Response: Response{StatusCode: 200, Body: svc},
|
||||||
}
|
}
|
||||||
response, err := c.Setup().Services(api.NamespaceDefault).Update(svc)
|
response, err := c.Setup().Services(ns).Update(svc)
|
||||||
c.Validate(t, response, err)
|
c.Validate(t, response, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteService(t *testing.T) {
|
func TestDeleteService(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "DELETE", Path: "/services/1"},
|
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/services/1")},
|
||||||
Response: Response{StatusCode: 200},
|
Response: Response{StatusCode: 200},
|
||||||
}
|
}
|
||||||
err := c.Setup().Services(api.NamespaceDefault).Delete("1")
|
err := c.Setup().Services(ns).Delete("1")
|
||||||
c.Validate(t, nil, err)
|
c.Validate(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListEndpooints(t *testing.T) {
|
func TestListEndpooints(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/endpoints"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/endpoints")},
|
||||||
Response: Response{StatusCode: 200,
|
Response: Response{StatusCode: 200,
|
||||||
Body: &api.EndpointsList{
|
Body: &api.EndpointsList{
|
||||||
Items: []api.Endpoints{
|
Items: []api.Endpoints{
|
||||||
@ -578,16 +600,17 @@ func TestListEndpooints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receivedEndpointsList, err := c.Setup().Endpoints(api.NamespaceDefault).List(labels.Everything())
|
receivedEndpointsList, err := c.Setup().Endpoints(ns).List(labels.Everything())
|
||||||
c.Validate(t, receivedEndpointsList, err)
|
c.Validate(t, receivedEndpointsList, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetEndpoints(t *testing.T) {
|
func TestGetEndpoints(t *testing.T) {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/endpoints/endpoint-1"},
|
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/endpoints/endpoint-1")},
|
||||||
Response: Response{StatusCode: 200, Body: &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "endpoint-1"}}},
|
Response: Response{StatusCode: 200, Body: &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "endpoint-1"}}},
|
||||||
}
|
}
|
||||||
response, err := c.Setup().Endpoints(api.NamespaceDefault).Get("endpoint-1")
|
response, err := c.Setup().Endpoints(ns).Get("endpoint-1")
|
||||||
c.Validate(t, response, err)
|
c.Validate(t, response, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ func (r *Request) Namespace(namespace string) *Request {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
if len(namespace) > 0 {
|
if len(namespace) > 0 {
|
||||||
return r.setParam("namespace", namespace)
|
return r.Path("ns").Path(namespace)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ func TestCreateReplica(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Spec: controllerSpec.Spec.Template.Spec,
|
Spec: controllerSpec.Spec.Template.Spec,
|
||||||
}
|
}
|
||||||
fakeHandler.ValidateRequest(t, makeURL("/pods?namespace=default"), "POST", nil)
|
fakeHandler.ValidateRequest(t, makeURL("/ns/default/pods"), "POST", nil)
|
||||||
actualPod, err := client.Codec.Decode([]byte(fakeHandler.RequestBody))
|
actualPod, err := client.Codec.Decode([]byte(fakeHandler.RequestBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %#v", err)
|
t.Errorf("Unexpected error: %#v", err)
|
||||||
|
@ -47,14 +47,14 @@ func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runtime.Object, error) {
|
func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runtime.Object, error) {
|
||||||
return m.RESTClient.Get().Path(m.Resource).Namespace(namespace).Path(name).SelectorParam("labels", selector).Do().Get()
|
return m.RESTClient.Get().Namespace(namespace).Path(m.Resource).Path(name).SelectorParam("labels", selector).Do().Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
|
func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
|
||||||
return m.RESTClient.Get().
|
return m.RESTClient.Get().
|
||||||
Path("watch").
|
Path("watch").
|
||||||
Path(m.Resource).
|
|
||||||
Namespace(namespace).
|
Namespace(namespace).
|
||||||
|
Path(m.Resource).
|
||||||
Param("resourceVersion", resourceVersion).
|
Param("resourceVersion", resourceVersion).
|
||||||
SelectorParam("labels", labelSelector).
|
SelectorParam("labels", labelSelector).
|
||||||
SelectorParam("fields", fieldSelector).
|
SelectorParam("fields", fieldSelector).
|
||||||
@ -62,7 +62,7 @@ func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fie
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTHelper) Delete(namespace, name string) error {
|
func (m *RESTHelper) Delete(namespace, name string) error {
|
||||||
return m.RESTClient.Delete().Path(m.Resource).Namespace(namespace).Path(name).Do().Error()
|
return m.RESTClient.Delete().Namespace(namespace).Path(m.Resource).Path(name).Do().Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error {
|
func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error {
|
||||||
@ -95,7 +95,7 @@ func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createResource(c RESTClient, resourcePath, namespace string, data []byte) error {
|
func createResource(c RESTClient, resourcePath, namespace string, data []byte) error {
|
||||||
return c.Post().Path(resourcePath).Namespace(namespace).Body(data).Do().Error()
|
return c.Post().Namespace(namespace).Path(resourcePath).Body(data).Do().Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) error {
|
func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) error {
|
||||||
@ -138,5 +138,5 @@ func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateResource(c RESTClient, resourcePath, namespace, name string, data []byte) error {
|
func updateResource(c RESTClient, resourcePath, namespace, name string, data []byte) error {
|
||||||
return c.Put().Path(resourcePath).Namespace(namespace).Path(name).Body(data).Do().Error()
|
return c.Put().Namespace(namespace).Path(resourcePath).Path(name).Body(data).Do().Error()
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,15 @@ func objBody(obj runtime.Object) io.ReadCloser {
|
|||||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), obj))))
|
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), obj))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splitPath returns the segments for a URL path.
|
||||||
|
func splitPath(path string) []string {
|
||||||
|
path = strings.Trim(path, "/")
|
||||||
|
if path == "" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return strings.Split(path, "/")
|
||||||
|
}
|
||||||
|
|
||||||
func TestRESTHelperDelete(t *testing.T) {
|
func TestRESTHelperDelete(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Err bool
|
Err bool
|
||||||
@ -65,12 +74,13 @@ func TestRESTHelperDelete(t *testing.T) {
|
|||||||
t.Errorf("unexpected method: %#v", req)
|
t.Errorf("unexpected method: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
parts := splitPath(req.URL.Path)
|
||||||
t.Errorf("url doesn't contain name: %#v", req)
|
if parts[1] != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if req.URL.Query().Get("namespace") != "bar" {
|
if parts[2] != "foo" {
|
||||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -105,7 +115,8 @@ func TestRESTHelperCreate(t *testing.T) {
|
|||||||
t.Errorf("unexpected method: %#v", req)
|
t.Errorf("unexpected method: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if req.URL.Query().Get("namespace") != "bar" {
|
parts := splitPath(req.URL.Path)
|
||||||
|
if parts[1] != "bar" {
|
||||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -230,12 +241,13 @@ func TestRESTHelperGet(t *testing.T) {
|
|||||||
t.Errorf("unexpected method: %#v", req)
|
t.Errorf("unexpected method: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
parts := splitPath(req.URL.Path)
|
||||||
t.Errorf("url doesn't contain name: %#v", req)
|
if parts[1] != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if req.URL.Query().Get("namespace") != "bar" {
|
if parts[2] != "foo" {
|
||||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -273,12 +285,13 @@ func TestRESTHelperUpdate(t *testing.T) {
|
|||||||
t.Errorf("unexpected method: %#v", req)
|
t.Errorf("unexpected method: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
parts := splitPath(req.URL.Path)
|
||||||
t.Errorf("url doesn't contain name: %#v", req)
|
if parts[1] != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if req.URL.Query().Get("namespace") != "bar" {
|
if parts[2] != "foo" {
|
||||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -192,7 +192,7 @@ func TestDefaultErrorFunc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
// FakeHandler musn't be sent requests other than the one you want to test.
|
// FakeHandler musn't be sent requests other than the one you want to test.
|
||||||
mux.Handle("/api/"+testapi.Version()+"/pods/foo", &handler)
|
mux.Handle("/api/"+testapi.Version()+"/ns/bar/pods/foo", &handler)
|
||||||
server := httptest.NewServer(mux)
|
server := httptest.NewServer(mux)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
factory := NewConfigFactory(client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()}))
|
factory := NewConfigFactory(client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()}))
|
||||||
@ -213,7 +213,7 @@ func TestDefaultErrorFunc(t *testing.T) {
|
|||||||
if !exists {
|
if !exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/pods/foo?namespace=bar", "GET", nil)
|
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/ns/bar/pods/foo", "GET", nil)
|
||||||
if e, a := testPod, got; !reflect.DeepEqual(e, a) {
|
if e, a := testPod, got; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("Expected %v, got %v", e, a)
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user