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:
@@ -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)
|
||||
|
Reference in New Issue
Block a user