Split naming behavior out into objects that are derived from the request

Fix bug with cross namespace queries not being possible.  Ensure selflink
is set on lists correctly.
This commit is contained in:
Clayton Coleman
2015-02-12 14:21:47 -05:00
parent 64678b71f3
commit 9175082a1c
5 changed files with 590 additions and 232 deletions

View File

@@ -154,6 +154,9 @@ type SimpleRESTStorage struct {
updated *Simple
created *Simple
actualNamespace string
namespacePresent bool
// These are set when Watch is called
fakeWatch *watch.FakeWatcher
requestedLabelSelector labels.Selector
@@ -172,6 +175,7 @@ type SimpleRESTStorage struct {
}
func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
storage.checkContext(ctx)
result := &SimpleList{
Items: storage.list,
}
@@ -179,10 +183,16 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Sele
}
func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
storage.checkContext(ctx)
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
}
func (storage *SimpleRESTStorage) checkContext(ctx api.Context) {
storage.actualNamespace, storage.namespacePresent = api.NamespaceFrom(ctx)
}
func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (runtime.Object, error) {
storage.checkContext(ctx)
storage.deleted = id
if err := storage.errors["delete"]; err != nil {
return nil, err
@@ -204,6 +214,7 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object {
}
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
storage.checkContext(ctx)
storage.created = obj.(*Simple)
if err := storage.errors["create"]; err != nil {
return nil, err
@@ -216,6 +227,7 @@ func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (r
}
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
storage.checkContext(ctx)
storage.updated = obj.(*Simple)
if err := storage.errors["update"]; err != nil {
return nil, false, err
@@ -229,6 +241,7 @@ func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (r
// Implement ResourceWatcher.
func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
storage.checkContext(ctx)
storage.requestedLabelSelector = label
storage.requestedFieldSelector = field
storage.requestedResourceVersion = resourceVersion
@@ -242,6 +255,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
// Implement Redirector.
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
storage.checkContext(ctx)
// validate that the namespace context on the request matches the expected input
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
@@ -388,29 +402,57 @@ func TestVersion(t *testing.T) {
}
}
func TestSimpleList(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
namespace: "other",
expectedSet: "/prefix/version/simple?namespace=other",
func TestList(t *testing.T) {
testCases := []struct {
url string
namespace string
selfLink string
legacy bool
}{
{"/prefix/version/simple", "", "/prefix/version/simple?namespace=", true},
{"/prefix/version/simple?namespace=other", "other", "/prefix/version/simple?namespace=other", true},
// list items across all namespaces
{"/prefix/version/simple?namespace=", "", "/prefix/version/simple?namespace=", true},
{"/prefix/version/namespaces/default/simple", "default", "/prefix/version/namespaces/default/simple", false},
{"/prefix/version/namespaces/other/simple", "other", "/prefix/version/namespaces/other/simple", false},
// list items across all namespaces
{"/prefix/version/simple", "", "/prefix/version/simple", false},
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
for i, testCase := range testCases {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace}
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
namespace: testCase.namespace,
expectedSet: testCase.selfLink,
}
var handler http.Handler
if testCase.legacy {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
} else {
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper)
}
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
}
if !selfLinker.called {
t.Errorf("Never set self link")
resp, err := http.Get(server.URL + testCase.url)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
if resp.StatusCode != http.StatusOK {
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
}
// TODO: future, restore get links
if !selfLinker.called {
t.Errorf("%d: never set self link", i)
}
if !simpleStorage.namespacePresent {
t.Errorf("%d: namespace not set", i)
} else if simpleStorage.actualNamespace != testCase.namespace {
t.Errorf("%d: unexpected resource namespace: %s", i, simpleStorage.actualNamespace)
}
}
}
@@ -426,7 +468,7 @@ func TestErrorList(t *testing.T) {
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusInternalServerError {
@@ -439,7 +481,54 @@ func TestNonEmptyList(t *testing.T) {
simpleStorage := SimpleRESTStorage{
list: []Simple{
{
TypeMeta: api.TypeMeta{Kind: "Simple"},
ObjectMeta: api.ObjectMeta{Name: "something", Namespace: "other"},
Other: "foo",
},
},
}
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
body, _ := ioutil.ReadAll(resp.Body)
t.Logf("Data: %s", string(body))
}
var listOut SimpleList
body, err := extractBody(resp, &listOut)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(listOut.Items) != 1 {
t.Errorf("Unexpected response: %#v", listOut)
return
}
if listOut.Items[0].Other != simpleStorage.list[0].Other {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
}
if listOut.SelfLink != "/prefix/version/simple?namespace=" {
t.Errorf("unexpected list self link: %#v", listOut)
}
expectedSelfLink := "/prefix/version/simple/something?namespace=other"
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
}
}
func TestSelfLinkSkipsEmptyName(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
list: []Simple{
{
ObjectMeta: api.ObjectMeta{Namespace: "other"},
Other: "foo",
},
@@ -452,7 +541,7 @@ func TestNonEmptyList(t *testing.T) {
resp, err := http.Get(server.URL + "/prefix/version/simple")
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
@@ -460,11 +549,10 @@ func TestNonEmptyList(t *testing.T) {
body, _ := ioutil.ReadAll(resp.Body)
t.Logf("Data: %s", string(body))
}
var listOut SimpleList
body, err := extractBody(resp, &listOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if len(listOut.Items) != 1 {
@@ -474,7 +562,10 @@ func TestNonEmptyList(t *testing.T) {
if listOut.Items[0].Other != simpleStorage.list[0].Other {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
}
expectedSelfLink := "/prefix/version/simple?namespace=other"
if listOut.SelfLink != "/prefix/version/simple?namespace=" {
t.Errorf("unexpected list self link: %#v", listOut)
}
expectedSelfLink := ""
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
}
@@ -817,6 +908,48 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
}
}
// when the object name and namespace can't be retrieved, skip name checking
func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
err: fmt.Errorf("test error"),
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
item := &Simple{
ObjectMeta: api.ObjectMeta{
Name: ID,
Namespace: "other", // does not match request
},
Other: "bar",
}
body, err := codec.Encode(item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
}
if selfLinker.called {
t.Errorf("self link ignored")
}
}
func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
@@ -931,20 +1064,79 @@ type setTestSelfLinker struct {
name string
namespace string
called bool
err error
}
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, nil }
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, nil }
func (*setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", nil }
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err }
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, s.err }
func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", s.err }
func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error {
if e, a := s.expectedSet, selfLink; e != a {
s.t.Errorf("expected '%v', got '%v'", e, a)
}
s.called = true
return nil
return s.err
}
func TestCreate(t *testing.T) {
storage := SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
time.Sleep(5 * time.Millisecond)
return obj, nil
},
}
selfLinker := &setTestSelfLinker{
t: t,
name: "bar",
namespace: "default",
expectedSet: "/prefix/version/foo/bar?namespace=default",
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
simple := &Simple{
Other: "bar",
}
data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
wg := sync.WaitGroup{}
wg.Add(1)
var response *http.Response
go func() {
response, err = client.Do(request)
wg.Done()
}()
wg.Wait()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var itemOut Simple
body, err := extractBody(response, &itemOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(&itemOut, simple) {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
}
if response.StatusCode != http.StatusCreated {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
}
if !selfLinker.called {
t.Errorf("Never set self link")
}
}
func TestCreateInNamespace(t *testing.T) {
storage := SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
time.Sleep(5 * time.Millisecond)