diff --git a/pkg/api/testapi/testapi.go b/pkg/api/testapi/testapi.go index b65c612bd5f..1d8cc5bfb2e 100644 --- a/pkg/api/testapi/testapi.go +++ b/pkg/api/testapi/testapi.go @@ -20,6 +20,7 @@ package testapi import ( "fmt" "os" + "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" @@ -76,14 +77,21 @@ func SelfLink(resource, name string) string { return fmt.Sprintf("/api/%s/%s/%s", Version(), resource, name) } -// Returns the appropriate path for the given resource, namespace and name. +// Returns the appropriate path for the given prefix (watch, proxy, redirect, etc), resource, namespace and name. // For ex, this is of the form: -// /api/v1beta1/pods/pod0 for v1beta1 and -// /api/v1beta3/namespaces/foo/pods/pod0 for v1beta3. -func ResourcePath(resource, namespace, name string) string { +// /api/v1beta1/watch/pods/pod0 for v1beta1 and +// /api/v1beta3/watch/namespaces/foo/pods/pod0 for v1beta3. +func ResourcePathWithPrefix(prefix, resource, namespace, name string) string { path := "/api/" + Version() - if !api.PreV1Beta3(Version()) && namespace != "" { - path = path + "/namespaces/" + namespace + if prefix != "" { + path = path + "/" + prefix + } + if !api.PreV1Beta3(Version()) { + if namespace != "" { + path = path + "/namespaces/" + namespace + } + // Resource names in v1beta3 are lower case. + resource = strings.ToLower(resource) } if resource != "" { path = path + "/" + resource @@ -94,6 +102,14 @@ func ResourcePath(resource, namespace, name string) string { return path } +// Returns the appropriate path for the given resource, namespace and name. +// For ex, this is of the form: +// /api/v1beta1/pods/pod0 for v1beta1 and +// /api/v1beta3/namespaces/foo/pods/pod0 for v1beta3. +func ResourcePath(resource, namespace, name string) string { + return ResourcePathWithPrefix("", resource, namespace, name) +} + // Returns the appropriate path along with the query params for the given resource, namespace and name. // For ex, this is of the form: // /api/v1beta1/pods/pod0?namespace=foo for v1beta1 and @@ -101,7 +117,7 @@ func ResourcePath(resource, namespace, name string) string { func ResourcePathWithQueryParams(resource, namespace, name string) string { path := ResourcePath(resource, namespace, name) // Add namespace as query param for pre v1beta3. - if api.PreV1Beta3(Version()) { + if api.PreV1Beta3(Version()) && namespace != "" { path = path + "?namespace=" + namespace } return path diff --git a/pkg/api/testapi/testapi_test.go b/pkg/api/testapi/testapi_test.go new file mode 100644 index 00000000000..af13e95cd78 --- /dev/null +++ b/pkg/api/testapi/testapi_test.go @@ -0,0 +1,169 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testapi + +import ( + "testing" +) + +func TestResourcePathWithPrefixForV1Beta3(t *testing.T) { + if Version() != "v1beta3" { + // Skip the test if we are not testing v1beta3. + return + } + + testCases := []struct { + prefix string + resource string + namespace string + name string + expected string + }{ + {"prefix", "resource", "mynamespace", "myresource", "/api/v1beta3/prefix/namespaces/mynamespace/resource/myresource"}, + {"prefix", "resource", "", "myresource", "/api/v1beta3/prefix/resource/myresource"}, + {"prefix", "resource", "mynamespace", "", "/api/v1beta3/prefix/namespaces/mynamespace/resource"}, + {"prefix", "resource", "", "", "/api/v1beta3/prefix/resource"}, + {"", "resource", "mynamespace", "myresource", "/api/v1beta3/namespaces/mynamespace/resource/myresource"}, + } + for _, item := range testCases { + if actual := ResourcePathWithPrefix(item.prefix, item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for prefix: %s, resource: %s, namespace: %s and name: %s", item.expected, actual, item.prefix, item.resource, item.namespace, item.name) + } + } +} + +func TestResourcePathWithPrefixForV1Beta1(t *testing.T) { + if Version() != "v1beta1" { + // Skip the test if we are not testing v1beta1. + return + } + + testCases := []struct { + prefix string + resource string + namespace string + name string + expected string + }{ + {"prefix", "resource", "mynamespace", "myresource", "/api/v1beta1/prefix/resource/myresource"}, + {"prefix", "resource", "", "myresource", "/api/v1beta1/prefix/resource/myresource"}, + {"prefix", "resource", "mynamespace", "", "/api/v1beta1/prefix/resource"}, + {"prefix", "resource", "", "", "/api/v1beta1/prefix/resource"}, + {"", "resource", "mynamespace", "myresource", "/api/v1beta1/resource/myresource"}, + } + for _, item := range testCases { + if actual := ResourcePathWithPrefix(item.prefix, item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for prefix: %s, resource: %s, namespace: %s and name: %s", item.expected, actual, item.prefix, item.resource, item.namespace, item.name) + } + } +} + +func TestResourcePathForV1Beta3(t *testing.T) { + if Version() != "v1beta3" { + // Skip the test if we are not testing v1beta3. + return + } + + testCases := []struct { + resource string + namespace string + name string + expected string + }{ + {"resource", "mynamespace", "myresource", "/api/v1beta3/namespaces/mynamespace/resource/myresource"}, + {"resource", "", "myresource", "/api/v1beta3/resource/myresource"}, + {"resource", "mynamespace", "", "/api/v1beta3/namespaces/mynamespace/resource"}, + {"resource", "", "", "/api/v1beta3/resource"}, + } + for _, item := range testCases { + if actual := ResourcePath(item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s, namespace: %s and name: %s", item.expected, actual, item.resource, item.namespace, item.name) + } + } +} + +func TestResourcePathForV1Beta1(t *testing.T) { + if Version() != "v1beta1" { + // Skip the test if we are not testing v1beta1. + return + } + + testCases := []struct { + resource string + namespace string + name string + expected string + }{ + {"resource", "mynamespace", "myresource", "/api/v1beta1/resource/myresource"}, + {"resource", "", "myresource", "/api/v1beta1/resource/myresource"}, + {"resource", "mynamespace", "", "/api/v1beta1/resource"}, + {"resource", "", "", "/api/v1beta1/resource"}, + } + for _, item := range testCases { + if actual := ResourcePath(item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s, namespace: %s and name: %s", item.expected, actual, item.resource, item.namespace, item.name) + } + } +} + +func TestResourcePathWithQueryParamsForV1Beta3(t *testing.T) { + if Version() != "v1beta3" { + // Skip the test if we are not testing v1beta3. + return + } + + testCases := []struct { + resource string + namespace string + name string + expected string + }{ + {"resource", "mynamespace", "myresource", "/api/v1beta3/namespaces/mynamespace/resource/myresource"}, + {"resource", "", "myresource", "/api/v1beta3/resource/myresource"}, + {"resource", "mynamespace", "", "/api/v1beta3/namespaces/mynamespace/resource"}, + {"resource", "", "", "/api/v1beta3/resource"}, + } + for _, item := range testCases { + if actual := ResourcePathWithQueryParams(item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s, namespace: %s and name: %s", item.expected, actual, item.resource, item.namespace, item.name) + } + } +} + +func TestResourcePathWithQueryParamsForV1Beta1(t *testing.T) { + if Version() != "v1beta1" { + // Skip the test if we are not testing v1beta1. + return + } + + testCases := []struct { + resource string + namespace string + name string + expected string + }{ + {"resource", "mynamespace", "myresource", "/api/v1beta1/resource/myresource?namespace=mynamespace"}, + {"resource", "", "myresource", "/api/v1beta1/resource/myresource"}, + {"resource", "mynamespace", "", "/api/v1beta1/resource?namespace=mynamespace"}, + {"resource", "", "", "/api/v1beta1/resource"}, + } + for _, item := range testCases { + if actual := ResourcePathWithQueryParams(item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s, namespace: %s and name: %s", item.expected, actual, item.resource, item.namespace, item.name) + } + } +} diff --git a/test/integration/auth_test.go b/test/integration/auth_test.go index ee5ce29ea0a..34efdd05e0f 100644 --- a/test/integration/auth_test.go +++ b/test/integration/auth_test.go @@ -30,10 +30,14 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "net/url" "os" + "strconv" "strings" "testing" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken" @@ -46,8 +50,15 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokentest" ) +var nodeResourceName string + func init() { requireEtcd() + if api.PreV1Beta3(testapi.Version()) { + nodeResourceName = "minions" + } else { + nodeResourceName = "nodes" + } } const ( @@ -63,114 +74,176 @@ func getTestTokenAuth() authenticator.Request { return bearertoken.New(tokenAuthenticator) } +func path(resource, namespace, name string) string { + return testapi.ResourcePath(resource, namespace, name) +} + +func pathWithQuery(resource, namespace, name string) string { + return testapi.ResourcePathWithQueryParams(resource, namespace, name) +} + +func pathWithPrefix(prefix, resource, namespace, name string) string { + return testapi.ResourcePathWithPrefix(prefix, resource, namespace, name) +} + +func timeoutPath(resource, namespace, name string) string { + return addTimeoutFlag(testapi.ResourcePath(resource, namespace, name)) +} + +func timeoutPathWithQuery(resource, namespace, name string) string { + return addTimeoutFlag(testapi.ResourcePathWithQueryParams(resource, namespace, name)) +} + // Bodies for requests used in subsequent tests. var aPod string = ` { "kind": "Pod", - "apiVersion": "v1beta1", - "id": "a", - "desiredState": { - "manifest": { - "version": "v1beta1", - "id": "a", - "containers": [{ "name": "foo", "image": "bar/foo" }] - } - }%s + "apiVersion": "v1beta3", + "metadata": { + "name": "a", + "creationTimestamp": null%s + }, + "spec": { + "containers": [ + { + "name": "foo", + "image": "bar/foo" + } + ] + } } ` var aRC string = ` { "kind": "ReplicationController", - "apiVersion": "v1beta1", - "id": "a", - "desiredState": { + "apiVersion": "v1beta3", + "metadata": { + "name": "a", + "labels": { + "name": "a" + }%s + }, + "spec": { "replicas": 2, - "replicaSelector": {"name": "a"}, - "podTemplate": { - "desiredState": { - "manifest": { - "version": "v1beta1", - "id": "a", - "containers": [{ - "name": "foo", - "image": "bar/foo" - }] + "selector": { + "name": "a" + }, + "template": { + "metadata": { + "labels": { + "name": "a" } }, - "labels": {"name": "a"} + "spec": { + "containers": [ + { + "name": "foo", + "image": "bar/foo" + } + ] + } } - }, - "labels": {"name": "a"}%s + } } ` var aService string = ` { "kind": "Service", - "apiVersion": "v1beta1", - "id": "a", - "port": 8000, - "portalIP": "10.0.0.100", - "labels": { "name": "a" }, - "selector": { "name": "a" }%s -} -` -var aMinion string = ` -{ - "kind": "Minion", - "apiVersion": "v1beta1", - "id": "a", - "resources": { - "capacity": { "memory": "10", "cpu": "10"} + "apiVersion": "v1beta3", + "metadata": { + "name": "a", + "labels": { + "name": "a" + }%s }, - "externalID": "external", - "hostIP": "10.10.10.10"%s + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 8000 + } + ], + "selector": { + "name": "a" + }, + "portalIP": "10.0.0.100" + } +} +` +var aNode string = ` +{ + "kind": "Node", + "apiVersion": "v1beta3", + "metadata": { + "name": "a"%s + }, + "spec": { + "externalID": "external" + } } ` - var aEvent string = ` { "kind": "Event", - "apiVersion": "v1beta1", - "id": "a", + "apiVersion": "v1beta3", + "metadata": { + "name": "a"%s + }, "involvedObject": { - "kind": "Minion", - "name": "a", + "kind": "Node", "namespace": "default", - "apiVersion": "v1beta1" - }%s + "name": "a", + "apiVersion": "v1beta3" + } } ` var aBinding string = ` { "kind": "Binding", - "apiVersion": "v1beta1", - "id": "a", - "host": "10.10.10.10", - "podID": "a"%s + "apiVersion": "v1beta3", + "metadata": { + "name": "a"%s + }, + "target": { + "name": "10.10.10.10" + } } ` var aEndpoints string = ` { "kind": "Endpoints", - "apiVersion": "v1beta1", - "id": "a", - "endpoints": ["10.10.1.1:1909"]%s + "apiVersion": "v1beta3", + "metadata": { + "name": "a"%s + }, + "subsets": [ + { + "addresses": [ + { + "IP": "10.10.1.1" + } + ], + "ports": [ + { + "port": 1909, + "protocol": "TCP" + } + ] + } + ] } ` var deleteNow string = ` { - "kind": "DeleteOptions", - "apiVersion": "v1beta1", - "gracePeriod": 0%s + "kind": "DeleteOptions", + "apiVersion": "v1beta3", + "gracePeriodSeconds": null%s } ` -// To ensure that a POST completes before a dependent GET, set a timeout. -var timeoutFlag = "?timeout=60s" - // Requests to try. Each one should be forbidden or not forbidden // depending on the authentication and authorization setup of the master. var code200 = map[int]bool{200: true} @@ -183,6 +256,15 @@ var code409 = map[int]bool{409: true} var code422 = map[int]bool{422: true} var code500 = map[int]bool{500: true} +// To ensure that a POST completes before a dependent GET, set a timeout. +func addTimeoutFlag(URLString string) string { + u, _ := url.Parse(URLString) + values := u.Query() + values.Set("timeout", "60s") + u.RawQuery = values.Encode() + return u.String() +} + func getTestRequests() []struct { verb string URL string @@ -196,86 +278,82 @@ func getTestRequests() []struct { statusCodes map[int]bool // Set of expected resp.StatusCode if all goes well. }{ // Normal methods on pods - {"GET", "/api/v1beta1/pods", "", code200}, - {"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code201}, - {"PUT", "/api/v1beta1/pods/a" + timeoutFlag, aPod, code200}, - {"GET", "/api/v1beta1/pods", "", code200}, - {"GET", "/api/v1beta1/pods/a", "", code200}, - {"PATCH", "/api/v1beta1/pods/a", "{%v}", code200}, - {"DELETE", "/api/v1beta1/pods/a" + timeoutFlag, deleteNow, code200}, + {"GET", path("pods", "", ""), "", code200}, + {"GET", path("pods", api.NamespaceDefault, ""), "", code200}, + {"POST", timeoutPath("pods", api.NamespaceDefault, ""), aPod, code201}, + {"PUT", timeoutPath("pods", api.NamespaceDefault, "a"), aPod, code200}, + {"GET", path("pods", api.NamespaceDefault, "a"), "", code200}, + {"PATCH", path("pods", api.NamespaceDefault, "a"), "{%v}", code200}, + {"DELETE", timeoutPath("pods", api.NamespaceDefault, "a"), deleteNow, code200}, // Non-standard methods (not expected to work, // but expected to pass/fail authorization prior to // failing validation. - {"OPTIONS", "/api/v1beta1/pods", "", code405}, - {"OPTIONS", "/api/v1beta1/pods/a", "", code405}, - {"HEAD", "/api/v1beta1/pods", "", code405}, - {"HEAD", "/api/v1beta1/pods/a", "", code405}, - {"TRACE", "/api/v1beta1/pods", "", code405}, - {"TRACE", "/api/v1beta1/pods/a", "", code405}, - {"NOSUCHVERB", "/api/v1beta1/pods", "", code405}, + {"OPTIONS", path("pods", api.NamespaceDefault, ""), "", code405}, + {"OPTIONS", path("pods", api.NamespaceDefault, "a"), "", code405}, + {"HEAD", path("pods", api.NamespaceDefault, ""), "", code405}, + {"HEAD", path("pods", api.NamespaceDefault, "a"), "", code405}, + {"TRACE", path("pods", api.NamespaceDefault, ""), "", code405}, + {"TRACE", path("pods", api.NamespaceDefault, "a"), "", code405}, + {"NOSUCHVERB", path("pods", api.NamespaceDefault, ""), "", code405}, // Normal methods on services - {"GET", "/api/v1beta1/services", "", code200}, - {"POST", "/api/v1beta1/services" + timeoutFlag, aService, code201}, - {"PUT", "/api/v1beta1/services/a" + timeoutFlag, aService, code200}, - {"GET", "/api/v1beta1/services", "", code200}, - {"GET", "/api/v1beta1/services/a", "", code200}, - {"DELETE", "/api/v1beta1/services/a" + timeoutFlag, "", code200}, + {"GET", path("services", "", ""), "", code200}, + {"GET", path("services", api.NamespaceDefault, ""), "", code200}, + {"POST", timeoutPath("services", api.NamespaceDefault, ""), aService, code201}, + {"PUT", timeoutPath("services", api.NamespaceDefault, "a"), aService, code200}, + {"GET", path("services", api.NamespaceDefault, "a"), "", code200}, + {"DELETE", timeoutPath("services", api.NamespaceDefault, "a"), "", code200}, // Normal methods on replicationControllers - {"GET", "/api/v1beta1/replicationControllers", "", code200}, - {"POST", "/api/v1beta1/replicationControllers" + timeoutFlag, aRC, code201}, - {"PUT", "/api/v1beta1/replicationControllers/a" + timeoutFlag, aRC, code200}, - {"GET", "/api/v1beta1/replicationControllers", "", code200}, - {"GET", "/api/v1beta1/replicationControllers/a", "", code200}, - {"DELETE", "/api/v1beta1/replicationControllers/a" + timeoutFlag, "", code200}, + {"GET", path("replicationControllers", "", ""), "", code200}, + {"GET", path("replicationControllers", api.NamespaceDefault, ""), "", code200}, + {"POST", timeoutPath("replicationControllers", api.NamespaceDefault, ""), aRC, code201}, + {"PUT", timeoutPath("replicationControllers", api.NamespaceDefault, "a"), aRC, code200}, + {"GET", path("replicationControllers", api.NamespaceDefault, "a"), "", code200}, + {"DELETE", timeoutPath("replicationControllers", api.NamespaceDefault, "a"), "", code200}, // Normal methods on endpoints - {"GET", "/api/v1beta1/endpoints", "", code200}, - {"POST", "/api/v1beta1/endpoints" + timeoutFlag, aEndpoints, code201}, - {"PUT", "/api/v1beta1/endpoints/a" + timeoutFlag, aEndpoints, code200}, - {"GET", "/api/v1beta1/endpoints", "", code200}, - {"GET", "/api/v1beta1/endpoints/a", "", code200}, - {"DELETE", "/api/v1beta1/endpoints/a" + timeoutFlag, "", code200}, + {"GET", path("endpoints", "", ""), "", code200}, + {"GET", path("endpoints", api.NamespaceDefault, ""), "", code200}, + {"POST", timeoutPath("endpoints", api.NamespaceDefault, ""), aEndpoints, code201}, + {"PUT", timeoutPath("endpoints", api.NamespaceDefault, "a"), aEndpoints, code200}, + {"GET", path("endpoints", api.NamespaceDefault, "a"), "", code200}, + {"DELETE", timeoutPath("endpoints", api.NamespaceDefault, "a"), "", code200}, // Normal methods on minions - {"GET", "/api/v1beta1/minions", "", code200}, - {"POST", "/api/v1beta1/minions" + timeoutFlag, aMinion, code201}, - {"PUT", "/api/v1beta1/minions/a" + timeoutFlag, aMinion, code200}, - {"GET", "/api/v1beta1/minions", "", code200}, - {"GET", "/api/v1beta1/minions/a", "", code200}, - {"DELETE", "/api/v1beta1/minions/a" + timeoutFlag, "", code200}, + {"GET", path(nodeResourceName, "", ""), "", code200}, + {"POST", timeoutPath(nodeResourceName, "", ""), aNode, code201}, + {"PUT", timeoutPath(nodeResourceName, "", "a"), aNode, code200}, + {"GET", path(nodeResourceName, "", "a"), "", code200}, + {"DELETE", timeoutPath(nodeResourceName, "", "a"), "", code200}, // Normal methods on events - {"GET", "/api/v1beta1/events", "", code200}, - {"POST", "/api/v1beta1/events" + timeoutFlag, aEvent, code201}, - {"PUT", "/api/v1beta1/events/a" + timeoutFlag, aEvent, code200}, - {"GET", "/api/v1beta1/events", "", code200}, - {"GET", "/api/v1beta1/events", "", code200}, - {"GET", "/api/v1beta1/events/a", "", code200}, - {"DELETE", "/api/v1beta1/events/a" + timeoutFlag, "", code200}, + {"GET", path("events", "", ""), "", code200}, + {"GET", path("events", api.NamespaceDefault, ""), "", code200}, + {"POST", timeoutPath("events", api.NamespaceDefault, ""), aEvent, code201}, + {"PUT", timeoutPath("events", api.NamespaceDefault, "a"), aEvent, code200}, + {"GET", path("events", api.NamespaceDefault, "a"), "", code200}, + {"DELETE", timeoutPath("events", api.NamespaceDefault, "a"), "", code200}, // Normal methods on bindings - {"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only - {"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code201}, // Need a pod to bind or you get a 404 - {"POST", "/api/v1beta1/bindings" + timeoutFlag, aBinding, code201}, - {"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code404}, - {"GET", "/api/v1beta1/bindings", "", code405}, - {"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances - {"DELETE", "/api/v1beta1/bindings/a" + timeoutFlag, "", code404}, + {"GET", path("bindings", api.NamespaceDefault, ""), "", code405}, + {"POST", timeoutPath("pods", api.NamespaceDefault, ""), aPod, code201}, // Need a pod to bind or you get a 404 + {"POST", timeoutPath("bindings", api.NamespaceDefault, ""), aBinding, code201}, + {"PUT", timeoutPath("bindings", api.NamespaceDefault, "a"), aBinding, code404}, + {"GET", path("bindings", api.NamespaceDefault, "a"), "", code404}, // No bindings instances + {"DELETE", timeoutPath("bindings", api.NamespaceDefault, "a"), "", code404}, // Non-existent object type. - {"GET", "/api/v1beta1/foo", "", code404}, - {"POST", "/api/v1beta1/foo", `{"foo": "foo"}`, code404}, - {"PUT", "/api/v1beta1/foo/a", `{"foo": "foo"}`, code404}, - {"GET", "/api/v1beta1/foo", "", code404}, - {"GET", "/api/v1beta1/foo/a", "", code404}, - {"DELETE", "/api/v1beta1/foo" + timeoutFlag, "", code404}, + {"GET", path("foo", "", ""), "", code404}, + {"POST", path("foo", api.NamespaceDefault, ""), `{"foo": "foo"}`, code404}, + {"PUT", path("foo", api.NamespaceDefault, "a"), `{"foo": "foo"}`, code404}, + {"GET", path("foo", api.NamespaceDefault, "a"), "", code404}, + {"DELETE", timeoutPath("foo", api.NamespaceDefault, ""), "", code404}, // Special verbs on nodes - {"GET", "/api/v1beta1/proxy/minions/a", "", code404}, - {"GET", "/api/v1beta1/redirect/minions/a", "", code404}, + {"GET", pathWithPrefix("proxy", nodeResourceName, api.NamespaceDefault, "a"), "", code404}, + {"GET", pathWithPrefix("redirect", nodeResourceName, api.NamespaceDefault, "a"), "", code404}, // TODO: test .../watch/..., which doesn't end before the test timeout. // TODO: figure out how to create a minion so that it can successfully proxy/redirect. @@ -299,7 +377,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -331,7 +409,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) { if r.verb == "PUT" { // For update operations, insert previous resource version if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 { - sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion) + sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion) } namespace := "default" sub += fmt.Sprintf(",\r\n\"namespace\": %q", namespace) @@ -367,6 +445,8 @@ func TestAuthModeAlwaysAllow(t *testing.T) { if err == nil { key := getPreviousResourceVersionKey(r.URL, id) previousResourceVersion[key] = currentResourceVersion + } else { + t.Logf("error in trying to extract resource version: %s", err) } } } @@ -380,10 +460,42 @@ func parseResourceVersion(response []byte) (string, float64, error) { if err != nil { return "", 0, fmt.Errorf("unexpected error unmarshaling resultBody: %v", err) } + apiVersion, ok := resultBodyMap["apiVersion"].(string) + if !ok { + return "", 0, fmt.Errorf("unexpected error, apiVersion not found in JSON response: %v", string(response)) + } + if api.PreV1Beta3(apiVersion) { + return parsePreV1Beta3ResourceVersion(resultBodyMap, response) + } + return parseV1Beta3ResourceVersion(resultBodyMap, response) +} + +func parseV1Beta3ResourceVersion(resultBodyMap map[string]interface{}, response []byte) (string, float64, error) { + metadata, ok := resultBodyMap["metadata"].(map[string]interface{}) + if !ok { + return "", 0, fmt.Errorf("unexpected error, metadata not found in JSON response: %v", string(response)) + } + id, ok := metadata["name"].(string) + if !ok { + return "", 0, fmt.Errorf("unexpected error, id not found in JSON response: %v", string(response)) + } + resourceVersionString, ok := metadata["resourceVersion"].(string) + if !ok { + return "", 0, fmt.Errorf("unexpected error, resourceVersion not found in JSON response: %v", string(response)) + } + resourceVersion, err := strconv.ParseFloat(resourceVersionString, 64) + if err != nil { + return "", 0, fmt.Errorf("unexpected error, could not parse resourceVersion as float64, err: %s. JSON response: %v", err, string(response)) + } + return id, resourceVersion, nil +} + +func parsePreV1Beta3ResourceVersion(resultBodyMap map[string]interface{}, response []byte) (string, float64, error) { id, ok := resultBodyMap["id"].(string) if !ok { return "", 0, fmt.Errorf("unexpected error, id not found in JSON response: %v", string(response)) } + resourceVersion, ok := resultBodyMap["resourceVersion"].(float64) if !ok { return "", 0, fmt.Errorf("unexpected error, resourceVersion not found in JSON response: %v", string(response)) @@ -405,7 +517,7 @@ func TestAuthModeAlwaysDeny(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -472,7 +584,7 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -506,7 +618,7 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) { if r.verb == "PUT" { // For update operations, insert previous resource version if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 { - sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion) + sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion) } namespace := "default" sub += fmt.Sprintf(",\r\n\"namespace\": %q", namespace) @@ -561,7 +673,7 @@ func TestBobIsForbidden(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -622,7 +734,7 @@ func TestUnknownUserIsUnauthorized(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -699,7 +811,7 @@ func TestNamespaceAuthorization(t *testing.T) { // This file has alice and bob in it. - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -736,20 +848,20 @@ func TestNamespaceAuthorization(t *testing.T) { statusCodes map[int]bool // allowed status codes. }{ - {"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=foo", "foo", aPod, code201}, - {"GET", "/api/v1beta1/pods?namespace=foo", "foo", "", code200}, - {"GET", "/api/v1beta1/pods/a?namespace=foo", "foo", "", code200}, - {"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=foo", "foo", "", code200}, + {"POST", timeoutPathWithQuery("pods", "foo", ""), "foo", aPod, code201}, + {"GET", pathWithQuery("pods", "foo", ""), "foo", "", code200}, + {"GET", pathWithQuery("pods", "foo", "a"), "foo", "", code200}, + {"DELETE", timeoutPathWithQuery("pods", "foo", "a"), "foo", "", code200}, - {"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=bar", "bar", aPod, code403}, - {"GET", "/api/v1beta1/pods?namespace=bar", "bar", "", code403}, - {"GET", "/api/v1beta1/pods/a?namespace=bar", "bar", "", code403}, - {"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=bar", "bar", "", code403}, + {"POST", timeoutPath("pods", "bar", ""), "bar", aPod, code403}, + {"GET", pathWithQuery("pods", "bar", ""), "bar", "", code403}, + {"GET", pathWithQuery("pods", "bar", "a"), "bar", "", code403}, + {"DELETE", timeoutPathWithQuery("pods", "bar", "a"), "bar", "", code403}, - {"POST", "/api/v1beta1/pods" + timeoutFlag, "", aPod, code403}, - {"GET", "/api/v1beta1/pods", "", "", code403}, - {"GET", "/api/v1beta1/pods/a", "", "", code403}, - {"DELETE", "/api/v1beta1/pods/a" + timeoutFlag, "", "", code403}, + {"POST", timeoutPath("pods", api.NamespaceDefault, ""), "", aPod, code403}, + {"GET", path("pods", "", ""), "", "", code403}, + {"GET", path("pods", api.NamespaceDefault, "a"), "", "", code403}, + {"DELETE", timeoutPath("pods", api.NamespaceDefault, "a"), "", "", code403}, } for _, r := range requests { @@ -760,7 +872,7 @@ func TestNamespaceAuthorization(t *testing.T) { if r.verb == "PUT" && r.body != "" { // For update operations, insert previous resource version if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 { - sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion) + sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion) } namespace := r.namespace if len(namespace) == 0 { @@ -814,7 +926,7 @@ func TestKindAuthorization(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -849,15 +961,15 @@ func TestKindAuthorization(t *testing.T) { body string statusCodes map[int]bool // allowed status codes. }{ - {"POST", "/api/v1beta1/services" + timeoutFlag, aService, code201}, - {"GET", "/api/v1beta1/services", "", code200}, - {"GET", "/api/v1beta1/services/a", "", code200}, - {"DELETE", "/api/v1beta1/services/a" + timeoutFlag, "", code200}, + {"POST", timeoutPath("services", api.NamespaceDefault, ""), aService, code201}, + {"GET", path("services", api.NamespaceDefault, ""), "", code200}, + {"GET", path("services", api.NamespaceDefault, "a"), "", code200}, + {"DELETE", timeoutPath("services", api.NamespaceDefault, "a"), "", code200}, - {"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code403}, - {"GET", "/api/v1beta1/pods", "", code403}, - {"GET", "/api/v1beta1/pods/a", "", code403}, - {"DELETE", "/api/v1beta1/pods/a" + timeoutFlag, "", code403}, + {"POST", timeoutPath("pods", api.NamespaceDefault, ""), aPod, code403}, + {"GET", path("pods", "", ""), "", code403}, + {"GET", path("pods", api.NamespaceDefault, "a"), "", code403}, + {"DELETE", timeoutPath("pods", api.NamespaceDefault, "a"), "", code403}, } for _, r := range requests { @@ -868,7 +980,7 @@ func TestKindAuthorization(t *testing.T) { if r.verb == "PUT" && r.body != "" { // For update operations, insert previous resource version if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 { - resourceVersionJson := fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion) + resourceVersionJson := fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion) bodyStr = fmt.Sprintf(r.body, resourceVersionJson) } } @@ -917,13 +1029,12 @@ func TestReadOnlyAuthorization(t *testing.T) { // Set up a master - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } - a := newAuthorizerWithContents(t, `{"readonly": true} -`) + a := newAuthorizerWithContents(t, `{"readonly": true}`) var m *master.Master s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { @@ -951,9 +1062,9 @@ func TestReadOnlyAuthorization(t *testing.T) { body string statusCodes map[int]bool // allowed status codes. }{ - {"POST", "/api/v1beta1/pods", aPod, code403}, - {"GET", "/api/v1beta1/pods", "", code200}, - {"GET", "/api/v1beta1/pods/a", "", code404}, + {"POST", path("pods", "", ""), aPod, code403}, + {"GET", path("pods", "", ""), "", code200}, + {"GET", path("pods", api.NamespaceDefault, "a"), "", code404}, } for _, r := range requests { diff --git a/test/integration/client_test.go b/test/integration/client_test.go index 8797aca6460..cf8e7b18c7e 100644 --- a/test/integration/client_test.go +++ b/test/integration/client_test.go @@ -30,6 +30,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" @@ -45,7 +46,7 @@ func init() { } func RunAMaster(t *testing.T) (*master.Master, *httptest.Server) { - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -73,76 +74,69 @@ func TestClient(t *testing.T) { _, s := RunAMaster(t) defer s.Close() - testCases := []string{ - "v1beta1", - "v1beta2", - "v1beta3", + ns := api.NamespaceDefault + deleteAllEtcdKeys() + client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()}) + + info, err := client.ServerVersion() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if e, a := version.Get(), *info; !reflect.DeepEqual(e, a) { + t.Errorf("expected %#v, got %#v", e, a) } - for _, apiVersion := range testCases { - ns := api.NamespaceDefault - deleteAllEtcdKeys() - client := client.NewOrDie(&client.Config{Host: s.URL, Version: apiVersion}) - info, err := client.ServerVersion() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if e, a := version.Get(), *info; !reflect.DeepEqual(e, a) { - t.Errorf("expected %#v, got %#v", e, a) - } + pods, err := client.Pods(ns).List(labels.Everything()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(pods.Items) != 0 { + t.Errorf("expected no pods, got %#v", pods) + } - pods, err := client.Pods(ns).List(labels.Everything()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if len(pods.Items) != 0 { - t.Errorf("expected no pods, got %#v", pods) - } - - // get a validation error - pod := &api.Pod{ - ObjectMeta: api.ObjectMeta{ - GenerateName: "test", - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "test", - }, + // get a validation error + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + GenerateName: "test", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", }, }, - } + }, + } - got, err := client.Pods(ns).Create(pod) - if err == nil { - t.Fatalf("unexpected non-error: %v", got) - } + got, err := client.Pods(ns).Create(pod) + if err == nil { + t.Fatalf("unexpected non-error: %v", got) + } - // get a created pod - pod.Spec.Containers[0].Image = "an-image" - got, err = client.Pods(ns).Create(pod) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got.Name == "" { - t.Errorf("unexpected empty pod Name %v", got) - } + // get a created pod + pod.Spec.Containers[0].Image = "an-image" + got, err = client.Pods(ns).Create(pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got.Name == "" { + t.Errorf("unexpected empty pod Name %v", got) + } - // pod is shown, but not scheduled - pods, err = client.Pods(ns).List(labels.Everything()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if len(pods.Items) != 1 { - t.Errorf("expected one pod, got %#v", pods) - } - actual := pods.Items[0] - if actual.Name != got.Name { - t.Errorf("expected pod %#v, got %#v", got, actual) - } - if actual.Spec.Host != "" { - t.Errorf("expected pod to be unscheduled, got %#v", actual) - } + // pod is shown, but not scheduled + pods, err = client.Pods(ns).List(labels.Everything()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(pods.Items) != 1 { + t.Errorf("expected one pod, got %#v", pods) + } + actual := pods.Items[0] + if actual.Name != got.Name { + t.Errorf("expected pod %#v, got %#v", got, actual) + } + if actual.Spec.Host != "" { + t.Errorf("expected pod to be unscheduled, got %#v", actual) } } @@ -159,7 +153,7 @@ func TestMultiWatch(t *testing.T) { defer s.Close() ns := api.NamespaceDefault - client := client.NewOrDie(&client.Config{Host: s.URL, Version: "v1beta1"}) + client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()}) dummyEvent := func(i int) *api.Event { name := fmt.Sprintf("unrelated-%v", i) diff --git a/test/integration/etcd_tools_test.go b/test/integration/etcd_tools_test.go index 1bb576ddd8e..97a26fd27a5 100644 --- a/test/integration/etcd_tools_test.go +++ b/test/integration/etcd_tools_test.go @@ -23,8 +23,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -93,9 +92,9 @@ func TestExtractObj(t *testing.T) { func TestWatch(t *testing.T) { client := newEtcdClient() - helper := tools.NewEtcdHelper(client, latest.Codec) + helper := tools.NewEtcdHelper(client, testapi.Codec()) withEtcdKey(func(key string) { - resp, err := client.Set(key, runtime.EncodeOrDie(v1beta1.Codec, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0) + resp, err := client.Set(key, runtime.EncodeOrDie(testapi.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/test/integration/secret_test.go b/test/integration/secret_test.go index 21f32322e50..a031df8d64f 100644 --- a/test/integration/secret_test.go +++ b/test/integration/secret_test.go @@ -26,6 +26,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/master" @@ -49,7 +50,7 @@ func deleteSecretOrErrorf(t *testing.T, c *client.Client, ns, name string) { // TestSecrets tests apiserver-side behavior of creation of secret objects and their use by pods. func TestSecrets(t *testing.T) { - helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") + helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -71,16 +72,9 @@ func TestSecrets(t *testing.T) { AdmissionControl: admit.NewAlwaysAdmit(), }) - testCases := []string{ - "v1beta1", - "v1beta2", - } - - for _, apiVersion := range testCases { - deleteAllEtcdKeys() - client := client.NewOrDie(&client.Config{Host: s.URL, Version: apiVersion}) - DoTestSecrets(t, client, apiVersion) - } + deleteAllEtcdKeys() + client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()}) + DoTestSecrets(t, client, testapi.Version()) } // DoTestSecrets test secrets for one api version.