Commit d167c11b authored by Clayton Coleman's avatar Clayton Coleman

Remove layers of indirection between apiinstaller and resthandler

Make the RESTHandler feel more go-restful, set the stage for adding new types of subresource collections.
parent 55e8357c
......@@ -29,6 +29,12 @@ import (
const (
StatusUnprocessableEntity = 422
StatusTooManyRequests = 429
// HTTP recommendations are for servers to define 5xx error codes
// for scenarios not covered by behavior. In this case, TryAgainLater
// is an indication that a transient server error has occured and the
// client *should* retry, with an optional Retry-After header to specify
// the back off window.
StatusTryAgainLater = 504
)
// StatusError is an error intended for consumption by a REST API server; it can also be
......@@ -202,6 +208,17 @@ func NewInternalError(err error) error {
}}
}
// NewTimeoutError returns an error indicating that a timeout occurred before the request
// could be completed. Clients may retry, but the operation may still complete.
func NewTimeoutError(message string) error {
return &StatusError{api.Status{
Status: api.StatusFailure,
Code: StatusTryAgainLater,
Reason: api.StatusReasonTimeout,
Message: fmt.Sprintf("Timeout: %s", message),
}}
}
// IsNotFound returns true if the specified error was created by NewNotFoundErr.
func IsNotFound(err error) bool {
return reasonForError(err) == api.StatusReasonNotFound
......
......@@ -89,9 +89,9 @@ type defaultAPIServer struct {
// as RESTful resources at prefix, serialized by codec, and also includes the support
// http resources.
// Note: This method is used only in tests.
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
prefix := root + "/" + version
group := NewAPIGroupVersion(storage, codec, root, prefix, selfLinker, admissionControl, mapper)
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
prefix := path.Join(root, version)
group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, mapper)
container := restful.NewContainer()
container.Router(restful.CurlyRouter{})
mux := container.ServeMux
......@@ -102,16 +102,19 @@ func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, ve
return &defaultAPIServer{mux, group}
}
// TODO: This is a whole API version right now. Maybe should rename it.
// APIGroupVersion is a http.Handler that exposes multiple RESTStorage objects
// APIGroupVersion is a helper for exposing RESTStorage objects as http.Handlers via go-restful
// It handles URLs of the form:
// /${storage_key}[/${object_name}]
// Where 'storage_key' points to a RESTStorage object stored in storage.
//
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
type APIGroupVersion struct {
handler RESTHandler
storage map[string]RESTStorage
codec runtime.Codec
prefix string
linker runtime.SelfLinker
admit admission.Interface
mapper meta.RESTMapper
// TODO: put me into a cleaner interface
info *APIRequestInfoResolver
}
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their
......@@ -119,18 +122,15 @@ type APIGroupVersion struct {
// This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server.
// TODO: add multitype codec serialization
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, apiRoot, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
return &APIGroupVersion{
handler: RESTHandler{
storage: storage,
codec: codec,
canonicalPrefix: canonicalPrefix,
selfLinker: selfLinker,
ops: NewOperations(),
admissionControl: admissionControl,
apiRequestInfoResolver: &APIRequestInfoResolver{util.NewStringSet(apiRoot), latest.RESTMapper},
},
mapper: mapper,
storage: storage,
codec: codec,
prefix: prefix,
linker: linker,
admit: admissionControl,
mapper: mapper,
info: &APIRequestInfoResolver{util.NewStringSet(root), latest.RESTMapper},
}
}
......@@ -139,7 +139,12 @@ func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, api
// in a slash. A restful WebService is created for the group and version.
func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, version string) error {
prefix := path.Join(root, version)
ws, registrationErrors := (&APIInstaller{prefix, version, &g.handler, g.mapper}).Install()
installer := &APIInstaller{
group: g,
prefix: prefix,
version: version,
}
ws, registrationErrors := installer.Install()
container.Add(ws)
return errors.NewAggregate(registrationErrors)
}
......@@ -186,15 +191,15 @@ func AddApiWebService(container *restful.Container, apiPrefix string, versions [
// TODO: InstallREST should register each version automatically
versionHandler := APIVersionHandler(versions[:]...)
getApiVersionsWebService := new(restful.WebService)
getApiVersionsWebService.Path(apiPrefix)
getApiVersionsWebService.Doc("get available api versions")
getApiVersionsWebService.Route(getApiVersionsWebService.GET("/").To(versionHandler).
Doc("get available api versions").
Operation("getApiVersions").
ws := new(restful.WebService)
ws.Path(apiPrefix)
ws.Doc("get available API versions")
ws.Route(ws.GET("/").To(versionHandler).
Doc("get available API versions").
Operation("getAPIVersions").
Produces(restful.MIME_JSON).
Consumes(restful.MIME_JSON))
container.Add(getApiVersionsWebService)
container.Add(ws)
}
// handleVersion writes the server's version information.
......
......@@ -489,7 +489,9 @@ func TestGet(t *testing.T) {
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/id",
expectedSet: "/prefix/version/simple/id?namespace=default",
name: "id",
namespace: "default",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
......@@ -497,6 +499,12 @@ func TestGet(t *testing.T) {
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple/id")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected response: %#v", resp)
}
var itemOut Simple
body, err := extractBody(resp, &itemOut)
if err != nil {
......@@ -511,6 +519,81 @@ func TestGet(t *testing.T) {
}
}
func TestGetAlternateSelfLink(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
item: Simple{
Other: "foo",
},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/id?namespace=test",
name: "id",
namespace: "test",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, legacyNamespaceMapper)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple/id?namespace=test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected response: %#v", resp)
}
var itemOut Simple
body, err := extractBody(resp, &itemOut)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if itemOut.Name != simpleStorage.item.Name {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
}
if !selfLinker.called {
t.Errorf("Never set self link")
}
}
func TestGetNamespaceSelfLink(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
item: Simple{
Other: "foo",
},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/namespaces/foo/simple/id",
name: "id",
namespace: "foo",
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, namespaceMapper)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/namespaces/foo/simple/id")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected response: %#v", resp)
}
var itemOut Simple
body, err := extractBody(resp, &itemOut)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if itemOut.Name != simpleStorage.item.Name {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
}
if !selfLinker.called {
t.Errorf("Never set self link")
}
}
func TestGetMissing(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
......@@ -542,11 +625,13 @@ func TestDelete(t *testing.T) {
client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
_, err = client.Do(request)
res, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %#v", res)
}
if simpleStorage.deleted != ID {
t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
}
......@@ -602,13 +687,19 @@ func TestUpdate(t *testing.T) {
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/" + ID,
expectedSet: "/prefix/version/simple/" + ID + "?namespace=default",
name: ID,
namespace: api.NamespaceDefault,
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
Namespace: "", // update should allow the client to send an empty namespace
},
Other: "bar",
}
body, err := codec.Encode(item)
......@@ -637,15 +728,15 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/" + ID,
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
Namespace: api.NamespaceDefault,
},
Other: "bar",
}
body, err := codec.Encode(item)
......@@ -665,6 +756,100 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
}
}
func TestUpdateRequiresMatchingName(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
Other: "bar",
}
body, err := codec.Encode(item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusBadRequest {
t.Errorf("Unexpected response %#v", response)
}
}
func TestUpdateAllowsMissingNamespace(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
},
Other: "bar",
}
body, err := codec.Encode(item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusOK {
t.Errorf("Unexpected response %#v", response)
}
}
func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
Namespace: "other",
},
Other: "bar",
}
body, err := codec.Encode(item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusBadRequest {
t.Errorf("Unexpected response %#v", response)
}
}
func TestUpdateMissing(t *testing.T) {
storage := map[string]RESTStorage{}
ID := "id"
......@@ -677,6 +862,10 @@ func TestUpdateMissing(t *testing.T) {
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
Namespace: api.NamespaceDefault,
},
Other: "bar",
}
body, err := codec.Encode(item)
......@@ -690,7 +879,6 @@ func TestUpdateMissing(t *testing.T) {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusNotFound {
t.Errorf("Unexpected response %#v", response)
}
......@@ -961,7 +1149,7 @@ func TestCreateTimeout(t *testing.T) {
simple := &Simple{Other: "foo"}
data, _ := codec.Encode(simple)
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?timeout=4ms", data, http.StatusAccepted)
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?timeout=4ms", data, apierrs.StatusTryAgainLater)
if itemOut.Status != api.StatusFailure || itemOut.Reason != api.StatusReasonTimeout {
t.Errorf("Unexpected status %#v", itemOut)
}
......
......@@ -35,7 +35,21 @@ func errToAPIStatus(err error) *api.Status {
switch t := err.(type) {
case statusError:
status := t.Status()
status.Status = api.StatusFailure
if len(status.Status) == 0 {
}
switch status.Status {
case api.StatusSuccess:
if status.Code == 0 {
status.Code = http.StatusOK
}
case "":
status.Status = api.StatusFailure
fallthrough
case api.StatusFailure:
if status.Code == 0 {
status.Code = http.StatusInternalServerError
}
}
//TODO: check for invalid responses
return &status
default:
......
......@@ -56,14 +56,22 @@ type RESTDeleter interface {
}
type RESTCreater interface {
// New returns an empty object that can be used with Create after request data has been put into it.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object
// Create creates a new version of a resource.
Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
}
type RESTUpdater interface {
// New returns an empty object that can be used with Update after request data has been put into it.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object
// Update finds a resource in the storage and updates it. Some implementations
// may allow updates creates the object - they should set the Created flag of
// the returned RESTResultto true. In the event of an asynchronous error returned
// the returned RESTResult to true. In the event of an asynchronous error returned
// via an api.Status object, the Created flag is ignored.
Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
}
......
/*
Copyright 2014 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 apiserver
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func TestFinishReq(t *testing.T) {
handler := &RESTHandler{codec: api.Codec}
op := &Operation{finished: &time.Time{}, result: RESTResult{Object: &api.Status{Code: http.StatusNotFound}}}
resp := httptest.NewRecorder()
handler.finishReq(op, nil, resp)
status := &api.Status{}
if err := json.Unmarshal([]byte(resp.Body.String()), status); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Code != http.StatusNotFound || status.Code != http.StatusNotFound {
t.Errorf("unexpected status: %#v", status)
}
}
func TestFinishReqUnwrap(t *testing.T) {
handler := &RESTHandler{codec: api.Codec}
op := &Operation{finished: &time.Time{}, result: RESTResult{Created: true, Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}}}
resp := httptest.NewRecorder()
handler.finishReq(op, nil, resp)
obj := &api.Pod{}
if err := json.Unmarshal([]byte(resp.Body.String()), obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Code != http.StatusCreated || obj.Name != "foo" {
t.Errorf("unexpected object: %#v", obj)
}
}
func TestFinishReqUnwrapStatus(t *testing.T) {
handler := &RESTHandler{codec: api.Codec}
op := &Operation{finished: &time.Time{}, result: RESTResult{Created: true, Object: &api.Status{Code: http.StatusNotFound}}}
resp := httptest.NewRecorder()
handler.finishReq(op, nil, resp)
obj := &api.Status{}
if err := json.Unmarshal([]byte(resp.Body.String()), obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Code != http.StatusNotFound || obj.Code != http.StatusNotFound {
t.Errorf("unexpected object: %#v", obj)
}
}
......@@ -37,24 +37,24 @@ import (
)
type WatchHandler struct {
storage map[string]RESTStorage
codec runtime.Codec
canonicalPrefix string
selfLinker runtime.SelfLinker
apiRequestInfoResolver *APIRequestInfoResolver
storage map[string]RESTStorage
codec runtime.Codec
prefix string
linker runtime.SelfLinker
info *APIRequestInfoResolver
}
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
name, err := h.selfLinker.Name(obj)
name, err := h.linker.Name(obj)
if err != nil {
return err
}
newURL := *req.URL
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
newURL.Path = path.Join(h.prefix, req.URL.Path, name)
newURL.RawQuery = ""
newURL.Fragment = ""
return h.selfLinker.SetSelfLink(obj, newURL.String())
return h.linker.SetSelfLink(obj, newURL.String())
}
func getWatchParams(query url.Values) (label, field labels.Selector, resourceVersion string, err error) {
......@@ -96,7 +96,7 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
requestInfo, err := h.info.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
httpCode = http.StatusNotFound
......
......@@ -78,6 +78,20 @@ var aPod string = `
}%s
}
`
var aPodInBar string = `
{
"kind": "Pod",
"apiVersion": "v1beta1",
"id": "a",
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "a",
"containers": [{ "name": "foo", "image": "bar/foo", }]
}
}%s
}
`
var aRC string = `
{
"kind": "ReplicationController",
......@@ -126,7 +140,6 @@ var aEvent string = `
{
"kind": "Event",
"apiVersion": "v1beta1",
"namespace": "default",
"id": "a",
"involvedObject": {
"kind": "Minion",
......@@ -316,14 +329,16 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
t.Logf("case %v", r)
var bodyStr string
if r.body != "" {
bodyStr = fmt.Sprintf(r.body, "")
sub := ""
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)
bodyStr = fmt.Sprintf(r.body, resourceVersionJson)
sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
}
namespace := "default"
sub += fmt.Sprintf(",\r\n\"namespace\": %v", namespace)
}
bodyStr = fmt.Sprintf(r.body, sub)
}
bodyBytes := bytes.NewReader([]byte(bodyStr))
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
......@@ -483,14 +498,16 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
t.Logf("case %v", r)
var bodyStr string
if r.body != "" {
bodyStr = fmt.Sprintf(r.body, "")
sub := ""
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)
bodyStr = fmt.Sprintf(r.body, resourceVersionJson)
sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
}
namespace := "default"
sub += fmt.Sprintf(",\r\n\"namespace\": %v", namespace)
}
bodyStr = fmt.Sprintf(r.body, sub)
}
bodyBytes := bytes.NewReader([]byte(bodyStr))
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
......@@ -705,24 +722,25 @@ func TestNamespaceAuthorization(t *testing.T) {
requests := []struct {
verb string
URL string
namespace string
body string
statusCodes map[int]bool // allowed status codes.
}{
{"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=foo", aPod, code200},
{"GET", "/api/v1beta1/pods?namespace=foo", "", code200},
{"GET", "/api/v1beta1/pods/a?namespace=foo", "", code200},
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=foo", "", code200},
{"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=foo", "foo", aPod, code200},
{"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", "/api/v1beta1/pods" + timeoutFlag + "&namespace=bar", aPod, code403},
{"GET", "/api/v1beta1/pods?namespace=bar", "", code403},
{"GET", "/api/v1beta1/pods/a?namespace=bar", "", code403},
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=bar", "", code403},
{"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", "/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", "/api/v1beta1/pods" + timeoutFlag, "", aPod, code403},
{"GET", "/api/v1beta1/pods", "", "", code403},
{"GET", "/api/v1beta1/pods/a", "", "", code403},
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag, "", "", code403},
}
for _, r := range requests {
......@@ -730,14 +748,19 @@ func TestNamespaceAuthorization(t *testing.T) {
t.Logf("case %v", r)
var bodyStr string
if r.body != "" {
bodyStr = fmt.Sprintf(r.body, "")
sub := ""
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)
bodyStr = fmt.Sprintf(r.body, resourceVersionJson)
sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
}
namespace := r.namespace
if len(namespace) == 0 {
namespace = "default"
}
sub += fmt.Sprintf(",\r\n\"namespace\": %v", namespace)
}
bodyStr = fmt.Sprintf(r.body, sub)
}
bodyBytes := bytes.NewReader([]byte(bodyStr))
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment