diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 7aef5f8577c..5fca34453ad 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -64,7 +64,12 @@ var newGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "ve var newVersion = newGroupVersion.String() var prefix = "apis" -var versions = []string{testVersion, newVersion} +var grouplessGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"} +var grouplessVersion = grouplessGroupVersion.String() +var grouplessPrefix = "api" +var grouplessCodec = runtime.CodecFor(api.Scheme, grouplessVersion) + +var versions = []string{grouplessVersion, testVersion, newVersion} var codec = runtime.CodecFor(api.Scheme, testVersion) var newCodec = runtime.CodecFor(api.Scheme, newVersion) @@ -89,6 +94,12 @@ func interfacesFor(version string) (*meta.VersionInterfaces, error) { ObjectConvertor: api.Scheme, MetadataAccessor: accessor, }, nil + case grouplessVersion: + return &meta.VersionInterfaces{ + Codec: grouplessCodec, + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil default: return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(versions, ", ")) } @@ -98,6 +109,22 @@ func newMapper() *meta.DefaultRESTMapper { return meta.NewDefaultRESTMapper("testgroup", versions, interfacesFor) } +func addGrouplessTypes() { + type ListOptions struct { + runtime.Object + unversioned.TypeMeta `json:",inline"` + LabelSelector string `json:"labelSelector,omitempty"` + FieldSelector string `json:"fieldSelector,omitempty"` + Watch bool `json:"watch,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` + } + api.Scheme.AddKnownTypes( + grouplessVersion, &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{}, + &ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) + api.Scheme.AddKnownTypes(grouplessVersion, &api.Pod{}) +} + func addTestTypes() { type ListOptions struct { runtime.Object @@ -137,6 +164,7 @@ func init() { api.Scheme.AddKnownTypes( "", &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{}, &api.ListOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) + addGrouplessTypes() addTestTypes() addNewTestTypes() @@ -160,6 +188,11 @@ func init() { admissionControl = admit.NewAlwaysAdmit() requestContextMapper = api.NewRequestContextMapper() + api.Scheme.AddFieldLabelConversionFunc(grouplessVersion, "Simple", + func(label, value string) (string, string, error) { + return label, value, nil + }, + ) api.Scheme.AddFieldLabelConversionFunc(testVersion, "Simple", func(label, value string) (string, string, error) { return label, value, nil @@ -175,76 +208,93 @@ func init() { // defaultAPIServer exposes nested objects for testability. type defaultAPIServer struct { http.Handler - group *APIGroupVersion container *restful.Container } // uses the default settings func handle(storage map[string]rest.Storage) http.Handler { - return handleInternal(true, storage, admissionControl, selfLinker) -} - -// uses the default settings for a v1 compatible api -func handleNew(storage map[string]rest.Storage) http.Handler { - return handleInternal(false, storage, admissionControl, selfLinker) + return handleInternal(storage, admissionControl, selfLinker) } // tests with a deny admission controller func handleDeny(storage map[string]rest.Storage) http.Handler { - return handleInternal(true, storage, deny.NewAlwaysDeny(), selfLinker) + return handleInternal(storage, deny.NewAlwaysDeny(), selfLinker) } // tests using the new namespace scope mechanism func handleNamespaced(storage map[string]rest.Storage) http.Handler { - return handleInternal(false, storage, admissionControl, selfLinker) + return handleInternal(storage, admissionControl, selfLinker) } // tests using a custom self linker func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler { - return handleInternal(true, storage, admissionControl, selfLinker) + return handleInternal(storage, admissionControl, selfLinker) } func newTestRequestInfoResolver() *RequestInfoResolver { return &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")} } -func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler { - group := &APIGroupVersion{ +func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler { + container := restful.NewContainer() + container.Router(restful.CurlyRouter{}) + mux := container.ServeMux + + template := APIGroupVersion{ Storage: storage, - Root: "/" + prefix, RequestInfoResolver: newTestRequestInfoResolver(), Creater: api.Scheme, Convertor: api.Scheme, Typer: api.Scheme, Linker: selfLinker, + Mapper: namespaceMapper, Admit: admissionControl, Context: requestContextMapper, } - if legacy { + + // groupless v1 version + { + group := template + group.Root = "/" + grouplessPrefix + group.GroupVersion = grouplessGroupVersion + group.ServerGroupVersion = &grouplessGroupVersion + group.Codec = grouplessCodec + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } + } + + // group version 1 + { + group := template + group.Root = "/" + prefix group.GroupVersion = testGroupVersion group.ServerGroupVersion = &testGroupVersion group.Codec = codec - group.Mapper = namespaceMapper - } else { + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } + } + + // group version 2 + { + group := template + group.Root = "/" + prefix group.GroupVersion = newGroupVersion group.ServerGroupVersion = &newGroupVersion group.Codec = newCodec - group.Mapper = namespaceMapper + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } } - container := restful.NewContainer() - container.Router(restful.CurlyRouter{}) - mux := container.ServeMux - if err := group.InstallREST(container); err != nil { - panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) - } ws := new(restful.WebService) InstallSupport(mux, ws, false) container.Add(ws) - return &defaultAPIServer{mux, group, container} + return &defaultAPIServer{mux, container} } func TestSimpleSetupRight(t *testing.T) { @@ -602,6 +652,33 @@ func TestNotFound(t *testing.T) { Status int } cases := map[string]T{ + // Positive checks to make sure everything is wired correctly + "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusOK}, + "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusOK}, + + "groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, + + "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/blah", http.StatusNotFound}, + "groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/", http.StatusNotFound}, + + "groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, + "groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/blah", http.StatusNotFound}, + "groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + "groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/", http.StatusNotFound}, + "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + // Positive checks to make sure everything is wired correctly "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, // TODO: JTL: "GET root item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusOK}, @@ -676,6 +753,16 @@ func TestUnimplementedRESTStorage(t *testing.T) { ErrCode int } cases := map[string]T{ + "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, + "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo", http.StatusNotFound}, + "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo", http.StatusNotFound}, + "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, + "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, + "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/foo", http.StatusNotFound}, + "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/foo/bar", http.StatusNotFound}, + "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/proxy/foo/bar", http.StatusNotFound}, + "groupless redirect object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/redirect/foo/bar", http.StatusNotFound}, + "GET object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, "GET list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, "POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, @@ -750,6 +837,84 @@ func TestList(t *testing.T) { label string field string }{ + // Groupless API + + // legacy namespace param is ignored + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=other", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + legacy: true, + label: "a=b", + field: "c=d", + }, + // legacy api version is honored + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + legacy: true, + label: "a=b", + field: "c=d", + }, + // list items across all namespaces + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + legacy: true, + }, + // list items in a namespace in the path + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/default/simple", + namespace: "default", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/default/simple", + }, + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + }, + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + label: "a=b", + field: "c=d", + }, + // list items across all namespaces + { + url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + }, + + // Group API + // legacy namespace param is ignored { url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=", @@ -833,12 +998,7 @@ func TestList(t *testing.T) { namespace: testCase.namespace, expectedSet: testCase.selfLink, } - var handler http.Handler - if testCase.legacy { - handler = handleLinker(storage, selfLinker) - } else { - handler = handleInternal(false, storage, admissionControl, selfLinker) - } + var handler = handleInternal(storage, admissionControl, selfLinker) server := httptest.NewServer(handler) defer server.Close() @@ -848,7 +1008,7 @@ func TestList(t *testing.T) { continue } if resp.StatusCode != http.StatusOK { - t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp) + t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp) body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) @@ -1263,7 +1423,7 @@ func TestGetNamespaceSelfLink(t *testing.T) { namespace: "foo", } storage["simple"] = &simpleStorage - handler := handleInternal(false, storage, admissionControl, selfLinker) + handler := handleInternal(storage, admissionControl, selfLinker) server := httptest.NewServer(handler) defer server.Close() @@ -2470,7 +2630,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) { namespace: "other", expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", } - handler := handleInternal(true, map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), selfLinker) + handler := handleInternal(map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), selfLinker) server := httptest.NewServer(handler) defer server.Close() client := http.Client{}