k3s/pkg/apiserver/apiserver_test.go

1065 lines
32 KiB
Go
Raw Normal View History

2014-06-06 23:40:48 +00:00
/*
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.
*/
2014-06-23 18:32:11 +00:00
2014-06-06 23:40:48 +00:00
package apiserver
import (
"bytes"
2014-07-31 18:16:13 +00:00
"encoding/json"
"errors"
2014-06-06 23:40:48 +00:00
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"sync"
2014-06-06 23:40:48 +00:00
"testing"
2014-06-25 20:21:32 +00:00
"time"
2014-06-17 01:03:44 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
2014-06-17 01:03:44 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
2014-07-31 18:16:13 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
2014-06-06 23:40:48 +00:00
)
2014-09-08 04:14:18 +00:00
func convert(obj runtime.Object) (runtime.Object, error) {
2014-07-16 21:30:28 +00:00
return obj, nil
}
// This creates a fake API version, similar to api/latest.go
const testVersion = "version"
var versions = []string{testVersion}
var codec = runtime.CodecFor(api.Scheme, testVersion)
var accessor = meta.NewAccessor()
var versioner runtime.ResourceVersioner = accessor
var selfLinker runtime.SelfLinker = accessor
var mapper meta.RESTMapper
var admissionControl admission.Interface
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
switch version {
case testVersion:
return &meta.VersionInterfaces{
Codec: codec,
ObjectConvertor: api.Scheme,
MetadataAccessor: accessor,
}, nil
default:
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(versions, ", "))
}
}
func init() {
// Certain API objects are returned regardless of the contents of storage:
// api.Status is returned in errors
// api.Operation/api.OperationList are returned by /operations
// "internal" version
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{},
&api.Status{}, &api.Operation{}, &api.OperationList{})
// "version" version
// TODO: Use versioned api objects?
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{},
&api.Status{}, &api.Operation{}, &api.OperationList{})
defMapper := meta.NewDefaultRESTMapper(
versions,
func(version string) (*meta.VersionInterfaces, bool) {
interfaces, err := interfacesFor(version)
if err != nil {
return nil, false
}
return interfaces, true
},
)
defMapper.Add(api.Scheme, true, versions...)
mapper = defMapper
admissionControl = admit.NewAlwaysAdmit()
}
2014-06-06 23:40:48 +00:00
type Simple struct {
api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata"`
Other string `json:"other,omitempty"`
2014-06-06 23:40:48 +00:00
}
2014-09-08 04:14:18 +00:00
func (*Simple) IsAnAPIObject() {}
2014-06-06 23:40:48 +00:00
type SimpleList struct {
api.TypeMeta `json:",inline"`
api.ListMeta `json:"metadata,inline"`
Items []Simple `json:"items,omitempty"`
2014-06-06 23:40:48 +00:00
}
2014-09-08 04:14:18 +00:00
func (*SimpleList) IsAnAPIObject() {}
func TestSimpleSetupRight(t *testing.T) {
s := &Simple{ObjectMeta: api.ObjectMeta{Name: "aName"}}
wire, err := codec.Encode(s)
if err != nil {
t.Fatal(err)
}
s2, err := codec.Decode(wire)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(s, s2) {
t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2)
}
}
2014-06-06 23:40:48 +00:00
type SimpleRESTStorage struct {
errors map[string]error
2014-06-06 23:40:48 +00:00
list []Simple
item Simple
deleted string
updated *Simple
created *Simple
2014-06-25 20:21:32 +00:00
// These are set when Watch is called
2014-10-30 16:55:17 +00:00
fakeWatch *watch.FakeWatcher
requestedLabelSelector labels.Selector
requestedFieldSelector labels.Selector
requestedResourceVersion string
requestedResourceNamespace string
// The id requested, and location to return for ResourceLocation
2014-08-25 21:36:15 +00:00
requestedResourceLocationID string
resourceLocation string
expectedResourceNamespace string
2014-08-25 21:36:15 +00:00
// If non-nil, called inside the WorkFunc when answering update, delete, create.
2014-07-28 13:15:50 +00:00
// obj receives the original input to the update, delete, or create call.
2014-09-08 04:14:18 +00:00
injectedFunction func(obj runtime.Object) (returnObj runtime.Object, err error)
2014-06-06 23:40:48 +00:00
}
2014-09-26 15:46:04 +00:00
func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
result := &SimpleList{
2014-06-06 23:40:48 +00:00
Items: storage.list,
}
return result, storage.errors["list"]
2014-06-06 23:40:48 +00:00
}
2014-09-26 15:46:04 +00:00
func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
2014-06-06 23:40:48 +00:00
}
func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (<-chan RESTResult, error) {
2014-06-06 23:40:48 +00:00
storage.deleted = id
if err := storage.errors["delete"]; err != nil {
return nil, err
2014-06-25 20:21:32 +00:00
}
2014-09-08 04:14:18 +00:00
return MakeAsync(func() (runtime.Object, error) {
2014-06-25 20:21:32 +00:00
if storage.injectedFunction != nil {
return storage.injectedFunction(&Simple{ObjectMeta: api.ObjectMeta{Name: id}})
2014-06-25 20:21:32 +00:00
}
return &api.Status{Status: api.StatusSuccess}, nil
2014-06-25 20:21:32 +00:00
}), nil
2014-06-06 23:40:48 +00:00
}
2014-09-08 04:14:18 +00:00
func (storage *SimpleRESTStorage) New() runtime.Object {
return &Simple{}
2014-06-06 23:40:48 +00:00
}
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
storage.created = obj.(*Simple)
if err := storage.errors["create"]; err != nil {
return nil, err
2014-06-25 20:21:32 +00:00
}
2014-09-08 04:14:18 +00:00
return MakeAsync(func() (runtime.Object, error) {
2014-06-25 20:21:32 +00:00
if storage.injectedFunction != nil {
return storage.injectedFunction(obj)
}
return obj, nil
}), nil
2014-06-06 23:40:48 +00:00
}
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
storage.updated = obj.(*Simple)
if err := storage.errors["update"]; err != nil {
return nil, err
2014-06-25 20:21:32 +00:00
}
2014-09-08 04:14:18 +00:00
return MakeAsync(func() (runtime.Object, error) {
2014-06-25 20:21:32 +00:00
if storage.injectedFunction != nil {
return storage.injectedFunction(obj)
}
return obj, nil
}), nil
2014-06-06 23:40:48 +00:00
}
// Implement ResourceWatcher.
func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
storage.requestedLabelSelector = label
storage.requestedFieldSelector = field
storage.requestedResourceVersion = resourceVersion
2014-10-30 16:55:17 +00:00
storage.requestedResourceNamespace = api.Namespace(ctx)
if err := storage.errors["watch"]; err != nil {
return nil, err
}
storage.fakeWatch = watch.NewFake()
return storage.fakeWatch, nil
}
2014-08-25 21:36:15 +00:00
// Implement Redirector.
2014-09-26 15:46:04 +00:00
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
// validate that the namespace context on the request matches the expected input
2014-10-30 16:55:17 +00:00
storage.requestedResourceNamespace = api.Namespace(ctx)
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
return "", fmt.Errorf("Expected request namespace %s, but got namespace %s", storage.expectedResourceNamespace, storage.requestedResourceNamespace)
}
2014-08-25 21:36:15 +00:00
storage.requestedResourceLocationID = id
if err := storage.errors["resourceLocation"]; err != nil {
return "", err
}
return storage.resourceLocation, nil
2014-08-25 21:36:15 +00:00
}
2014-09-08 04:14:18 +00:00
func extractBody(response *http.Response, object runtime.Object) (string, error) {
2014-06-06 23:40:48 +00:00
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return string(body), err
}
err = codec.DecodeInto(body, object)
2014-06-06 23:40:48 +00:00
return string(body), err
}
2014-07-31 18:16:13 +00:00
func TestNotFound(t *testing.T) {
type T struct {
Method string
Path string
Status int
2014-07-31 18:16:13 +00:00
}
cases := map[string]T{
"PATCH method": {"PATCH", "/prefix/version/foo", http.StatusMethodNotAllowed},
"GET long prefix": {"GET", "/prefix/", http.StatusNotFound},
"GET missing storage": {"GET", "/prefix/version/blah", http.StatusNotFound},
"GET with extra segment": {"GET", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"POST with extra segment": {"POST", "/prefix/version/foo/bar", http.StatusMethodNotAllowed},
"DELETE without extra segment": {"DELETE", "/prefix/version/foo", http.StatusMethodNotAllowed},
"DELETE with extra segment": {"DELETE", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"PUT without extra segment": {"PUT", "/prefix/version/foo", http.StatusMethodNotAllowed},
"PUT with extra segment": {"PUT", "/prefix/version/foo/bar/baz", http.StatusNotFound},
"watch missing storage": {"GET", "/prefix/version/watch/", http.StatusNotFound},
"watch with bad method": {"POST", "/prefix/version/watch/foo/bar", http.StatusNotFound},
2014-07-31 18:16:13 +00:00
}
handler := Handle(map[string]RESTStorage{
2014-07-31 18:16:13 +00:00
"foo": &SimpleRESTStorage{},
2015-01-06 19:26:17 +00:00
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-07-31 18:16:13 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-07-31 18:16:13 +00:00
client := http.Client{}
for k, v := range cases {
request, err := http.NewRequest(v.Method, server.URL+v.Path, nil)
2014-08-04 00:01:15 +00:00
if err != nil {
2014-10-20 22:23:28 +00:00
t.Fatalf("unexpected error: %v", err)
2014-08-04 00:01:15 +00:00
}
2014-07-31 18:16:13 +00:00
response, err := client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != v.Status {
2014-11-20 12:42:48 +00:00
t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response)
2014-07-31 18:16:13 +00:00
}
}
}
func TestVersion(t *testing.T) {
2015-01-06 19:26:17 +00:00
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-07-31 18:16:13 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-07-31 18:16:13 +00:00
client := http.Client{}
request, err := http.NewRequest("GET", server.URL+"/version", nil)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-07-31 18:16:13 +00:00
response, err := client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-07-31 18:16:13 +00:00
var info version.Info
err = json.NewDecoder(response.Body).Decode(&info)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-07-31 18:16:13 +00:00
if !reflect.DeepEqual(version.Get(), info) {
t.Errorf("Expected %#v, Got %#v", version.Get(), info)
}
}
2014-06-06 23:40:48 +00:00
func TestSimpleList(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
storage["simple"] = &simpleStorage
2014-09-26 00:20:28 +00:00
selfLinker := &setTestSelfLinker{
t: t,
2014-11-24 18:35:24 +00:00
namespace: "other",
expectedSet: "/prefix/version/simple?namespace=other",
2014-09-26 00:20:28 +00:00
}
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
resp, err := http.Get(server.URL + "/prefix/version/simple")
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
2014-06-06 23:40:48 +00:00
}
2014-09-26 00:20:28 +00:00
if !selfLinker.called {
t.Errorf("Never set self link")
}
2014-06-06 23:40:48 +00:00
}
func TestErrorList(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
errors: map[string]error{"list": fmt.Errorf("test Error")},
2014-06-06 23:40:48 +00:00
}
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
resp, err := http.Get(server.URL + "/prefix/version/simple")
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
if resp.StatusCode != http.StatusInternalServerError {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusInternalServerError, resp)
2014-06-06 23:40:48 +00:00
}
}
func TestNonEmptyList(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
list: []Simple{
2014-06-12 21:09:40 +00:00
{
2014-11-24 18:35:24 +00:00
TypeMeta: api.TypeMeta{Kind: "Simple"},
ObjectMeta: api.ObjectMeta{Namespace: "other"},
Other: "foo",
2014-06-06 23:40:48 +00:00
},
},
}
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
resp, err := http.Get(server.URL + "/prefix/version/simple")
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
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))
2014-06-06 23:40:48 +00:00
}
var listOut SimpleList
body, err := extractBody(resp, &listOut)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
if len(listOut.Items) != 1 {
t.Errorf("Unexpected response: %#v", listOut)
return
2014-06-06 23:40:48 +00:00
}
2014-10-22 17:02:02 +00:00
if listOut.Items[0].Other != simpleStorage.list[0].Other {
2014-06-06 23:40:48 +00:00
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
}
2014-11-24 18:35:24 +00:00
expectedSelfLink := "/prefix/version/simple?namespace=other"
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
}
2014-06-06 23:40:48 +00:00
}
func TestGet(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
item: Simple{
2014-10-22 17:02:02 +00:00
Other: "foo",
2014-06-06 23:40:48 +00:00
},
}
2014-09-26 00:20:28 +00:00
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/id",
}
2014-06-06 23:40:48 +00:00
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
resp, err := http.Get(server.URL + "/prefix/version/simple/id")
var itemOut Simple
body, err := extractBody(resp, &itemOut)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
if itemOut.Name != simpleStorage.item.Name {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
}
2014-09-26 00:20:28 +00:00
if !selfLinker.called {
t.Errorf("Never set self link")
}
2014-06-06 23:40:48 +00:00
}
func TestGetMissing(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
}
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
resp, err := http.Get(server.URL + "/prefix/version/simple/id")
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusNotFound {
t.Errorf("Unexpected response %#v", resp)
}
}
2014-06-06 23:40:48 +00:00
func TestDelete(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
_, err = client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
if simpleStorage.deleted != ID {
2014-07-18 19:03:22 +00:00
t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
2014-06-06 23:40:48 +00:00
}
}
func TestDeleteInvokesAdmissionControl(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
}
}
func TestDeleteMissing(t *testing.T) {
storage := map[string]RESTStorage{}
ID := "id"
simpleStorage := SimpleRESTStorage{
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
}
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
response, err := client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusNotFound {
t.Errorf("Unexpected response %#v", response)
}
}
2014-06-06 23:40:48 +00:00
func TestUpdate(t *testing.T) {
storage := map[string]RESTStorage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
2014-09-26 00:20:28 +00:00
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/prefix/version/simple/" + ID,
}
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
2014-09-08 04:14:18 +00:00
item := &Simple{
2014-10-22 17:02:02 +00:00
Other: "bar",
2014-06-06 23:40:48 +00:00
}
body, err := codec.Encode(item)
2014-08-04 00:01:15 +00:00
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
2014-08-04 00:01:15 +00:00
}
2014-06-06 23:40:48 +00:00
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
2014-06-06 23:40:48 +00:00
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
}
2014-09-26 00:20:28 +00:00
if !selfLinker.called {
t.Errorf("Never set self link")
}
2014-06-06 23:40:48 +00:00
}
func TestUpdateInvokesAdmissionControl(t *testing.T) {
storage := map[string]RESTStorage{}
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())
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.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
}
}
func TestUpdateMissing(t *testing.T) {
storage := map[string]RESTStorage{}
ID := "id"
simpleStorage := SimpleRESTStorage{
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
}
storage["simple"] = &simpleStorage
2015-01-06 19:26:17 +00:00
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-09-08 04:14:18 +00:00
item := &Simple{
2014-10-22 17:02:02 +00:00
Other: "bar",
}
body, err := codec.Encode(item)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("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)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusNotFound {
t.Errorf("Unexpected response %#v", response)
}
}
2014-06-06 23:40:48 +00:00
func TestCreate(t *testing.T) {
wait := sync.WaitGroup{}
wait.Add(1)
simpleStorage := &SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (returnObj runtime.Object, err error) {
wait.Wait()
return &Simple{}, nil
},
}
handler := Handle(map[string]RESTStorage{
2014-06-25 20:21:32 +00:00
"foo": simpleStorage,
2015-01-06 19:26:17 +00:00
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
2014-06-06 23:40:48 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-06-06 23:40:48 +00:00
client := http.Client{}
2014-09-08 04:14:18 +00:00
simple := &Simple{
2014-10-22 17:02:02 +00:00
Other: "foo",
2014-07-16 21:30:28 +00:00
}
data, _ := codec.Encode(simple)
2014-06-06 23:40:48 +00:00
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data))
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-06-06 23:40:48 +00:00
response, err := client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusAccepted {
2014-06-06 23:40:48 +00:00
t.Errorf("Unexpected response %#v", response)
}
2014-06-25 20:21:32 +00:00
var itemOut api.Status
2014-06-06 23:40:48 +00:00
body, err := extractBody(response, &itemOut)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
Evolve the api.Status object with Reason/Details Contains breaking API change on api.Status#Details (type change) Turn Details from string -> StatusDetails - a general bucket for keyed error behavior. Define an open enumeration ReasonType exposed as Reason on the status object to provide machine readable subcategorization beyond HTTP Status Code. Define a human readable field Message which is common convention (previously this was joined into Details). Precedence order: HTTP Status Code, Reason, Details. apiserver would impose restraints on the ReasonTypes defined by the main apiobject, and ensure their use is consistent. There are four long term scenarios this change supports: 1. Allow a client access to a machine readable field that can be easily switched on for improving or translating the generic server Message. 2. Return a 404 when a composite operation on multiple resources fails with enough data so that a client can distinguish which item does not exist. E.g. resource Parent and resource Child, POST /parents/1/children to create a new Child, but /parents/1 is deleted. POST returns 404, ReasonTypeNotFound, and Details.ID = "1", Details.Kind = "parent" 3. Allow a client to receive validation data that is keyed by attribute for building user facing UIs around field submission. Validation is usually expressed as map[string][]string, but that type is less appropriate for many other uses. 4. Allow specific API errors to return more granular failure status for specific operations. An example might be a minion proxy, where the operation that failed may be both proxying OR the minion itself. In this case a reason may be defined "proxy_failed" corresponding to 502, where the Details field may be extended to contain a nested error object. At this time only ID and Kind are exposed
2014-07-31 18:12:26 +00:00
if itemOut.Status != api.StatusWorking || itemOut.Details == nil || itemOut.Details.ID == "" {
2014-06-25 20:21:32 +00:00
t.Errorf("Unexpected status: %#v (%s)", itemOut, string(body))
}
wait.Done()
2014-06-25 20:21:32 +00:00
}
func TestCreateInvokesAdmissionControl(t *testing.T) {
wait := sync.WaitGroup{}
wait.Add(1)
simpleStorage := &SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (returnObj runtime.Object, err error) {
wait.Wait()
return &Simple{}, nil
},
}
handler := Handle(map[string]RESTStorage{
"foo": simpleStorage,
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
simple := &Simple{
Other: "foo",
}
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)
}
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
}
}
func TestCreateNotFound(t *testing.T) {
handler := Handle(map[string]RESTStorage{
2014-07-31 18:16:13 +00:00
"simple": &SimpleRESTStorage{
// storage.Create can fail with not found error in theory.
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
2014-07-31 18:16:13 +00:00
},
2015-01-06 19:26:17 +00:00
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
client := http.Client{}
2014-10-22 17:02:02 +00:00
simple := &Simple{Other: "foo"}
data, _ := codec.Encode(simple)
2014-07-31 18:16:13 +00:00
request, err := http.NewRequest("POST", server.URL+"/prefix/version/simple", bytes.NewBuffer(data))
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
response, err := client.Do(request)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusNotFound {
t.Errorf("Unexpected response %#v", response)
}
}
2014-06-25 20:21:32 +00:00
func TestParseTimeout(t *testing.T) {
if d := parseTimeout(""); d != 30*time.Second {
t.Errorf("blank timeout produces %v", d)
}
if d := parseTimeout("not a timeout"); d != 30*time.Second {
t.Errorf("bad timeout produces %v", d)
}
if d := parseTimeout("10s"); d != 10*time.Second {
t.Errorf("10s timeout produced: %v", d)
2014-06-06 23:40:48 +00:00
}
}
2014-09-26 00:20:28 +00:00
type setTestSelfLinker struct {
t *testing.T
expectedSet string
name string
2014-11-24 18:35:24 +00:00
namespace string
2014-09-26 00:20:28 +00:00
called bool
}
2014-11-24 18:35:24 +00:00
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 }
2014-09-26 00:20:28 +00:00
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
}
func TestSyncCreate(t *testing.T) {
storage := SimpleRESTStorage{
2014-09-08 04:14:18 +00:00
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
2014-07-31 18:16:13 +00:00
time.Sleep(5 * time.Millisecond)
2014-06-25 20:21:32 +00:00
return obj, nil
},
}
2014-09-26 00:20:28 +00:00
selfLinker := &setTestSelfLinker{
t: t,
name: "bar",
2014-11-24 18:35:24 +00:00
namespace: "other",
expectedSet: "/prefix/version/ns/other/foo/bar",
2014-09-26 00:20:28 +00:00
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
2015-01-06 19:26:17 +00:00
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
client := http.Client{}
2014-09-08 04:14:18 +00:00
simple := &Simple{
2014-10-22 17:02:02 +00:00
Other: "bar",
2014-07-16 21:30:28 +00:00
}
data, _ := codec.Encode(simple)
request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo?sync=true", bytes.NewBuffer(data))
2014-08-04 00:01:15 +00:00
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()
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var itemOut Simple
body, err := extractBody(response, &itemOut)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-09-08 04:14:18 +00:00
if !reflect.DeepEqual(&itemOut, simple) {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
}
2014-06-25 20:21:32 +00:00
if response.StatusCode != http.StatusOK {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
}
2014-09-26 00:20:28 +00:00
if !selfLinker.called {
t.Errorf("Never set self link")
}
}
2014-07-31 18:16:13 +00:00
func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *api.Status {
client := http.Client{}
request, err := http.NewRequest(method, url, bytes.NewBuffer(data))
if err != nil {
t.Fatalf("unexpected error %#v", err)
return nil
}
response, err := client.Do(request)
if err != nil {
t.Fatalf("unexpected error on %s %s: %v", method, url, err)
2014-07-31 18:16:13 +00:00
return nil
}
var status api.Status
_, err = extractBody(response, &status)
if err != nil {
t.Fatalf("unexpected error on %s %s: %v", method, url, err)
2014-07-31 18:16:13 +00:00
return nil
}
if code != response.StatusCode {
t.Fatalf("Expected %s %s to return %d, Got %d", method, url, code, response.StatusCode)
2014-07-31 18:16:13 +00:00
}
return &status
}
func TestAsyncDelayReturnsError(t *testing.T) {
storage := SimpleRESTStorage{
2014-09-08 04:14:18 +00:00
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
return nil, apierrs.NewAlreadyExists("foo", "bar")
2014-07-31 18:16:13 +00:00
},
}
2015-01-06 19:26:17 +00:00
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl)
handler.(*defaultAPIServer).group.handler.asyncOpWait = time.Millisecond / 2
2014-07-31 18:16:13 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-07-31 18:16:13 +00:00
status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/prefix/version/foo/bar", server.URL), nil, http.StatusConflict)
if status.Status != api.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != api.StatusReasonAlreadyExists {
2014-07-31 18:16:13 +00:00
t.Errorf("Unexpected status %#v", status)
}
}
func TestAsyncCreateError(t *testing.T) {
ch := make(chan struct{})
storage := SimpleRESTStorage{
2014-09-08 04:14:18 +00:00
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
2014-07-31 18:16:13 +00:00
<-ch
return nil, apierrs.NewAlreadyExists("foo", "bar")
2014-07-31 18:16:13 +00:00
},
}
2014-09-26 00:20:28 +00:00
selfLinker := &setTestSelfLinker{
t: t,
name: "bar",
2014-09-26 00:20:28 +00:00
expectedSet: "/prefix/version/foo/bar",
}
2015-01-06 19:26:17 +00:00
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
2014-07-31 18:16:13 +00:00
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-07-31 18:16:13 +00:00
2014-10-22 17:02:02 +00:00
simple := &Simple{Other: "bar"}
data, _ := codec.Encode(simple)
2014-07-31 18:16:13 +00:00
status := expectApiStatus(t, "POST", fmt.Sprintf("%s/prefix/version/foo", server.URL), data, http.StatusAccepted)
Evolve the api.Status object with Reason/Details Contains breaking API change on api.Status#Details (type change) Turn Details from string -> StatusDetails - a general bucket for keyed error behavior. Define an open enumeration ReasonType exposed as Reason on the status object to provide machine readable subcategorization beyond HTTP Status Code. Define a human readable field Message which is common convention (previously this was joined into Details). Precedence order: HTTP Status Code, Reason, Details. apiserver would impose restraints on the ReasonTypes defined by the main apiobject, and ensure their use is consistent. There are four long term scenarios this change supports: 1. Allow a client access to a machine readable field that can be easily switched on for improving or translating the generic server Message. 2. Return a 404 when a composite operation on multiple resources fails with enough data so that a client can distinguish which item does not exist. E.g. resource Parent and resource Child, POST /parents/1/children to create a new Child, but /parents/1 is deleted. POST returns 404, ReasonTypeNotFound, and Details.ID = "1", Details.Kind = "parent" 3. Allow a client to receive validation data that is keyed by attribute for building user facing UIs around field submission. Validation is usually expressed as map[string][]string, but that type is less appropriate for many other uses. 4. Allow specific API errors to return more granular failure status for specific operations. An example might be a minion proxy, where the operation that failed may be both proxying OR the minion itself. In this case a reason may be defined "proxy_failed" corresponding to 502, where the Details field may be extended to contain a nested error object. At this time only ID and Kind are exposed
2014-07-31 18:12:26 +00:00
if status.Status != api.StatusWorking || status.Details == nil || status.Details.ID == "" {
2014-07-31 18:16:13 +00:00
t.Errorf("Unexpected status %#v", status)
}
Evolve the api.Status object with Reason/Details Contains breaking API change on api.Status#Details (type change) Turn Details from string -> StatusDetails - a general bucket for keyed error behavior. Define an open enumeration ReasonType exposed as Reason on the status object to provide machine readable subcategorization beyond HTTP Status Code. Define a human readable field Message which is common convention (previously this was joined into Details). Precedence order: HTTP Status Code, Reason, Details. apiserver would impose restraints on the ReasonTypes defined by the main apiobject, and ensure their use is consistent. There are four long term scenarios this change supports: 1. Allow a client access to a machine readable field that can be easily switched on for improving or translating the generic server Message. 2. Return a 404 when a composite operation on multiple resources fails with enough data so that a client can distinguish which item does not exist. E.g. resource Parent and resource Child, POST /parents/1/children to create a new Child, but /parents/1 is deleted. POST returns 404, ReasonTypeNotFound, and Details.ID = "1", Details.Kind = "parent" 3. Allow a client to receive validation data that is keyed by attribute for building user facing UIs around field submission. Validation is usually expressed as map[string][]string, but that type is less appropriate for many other uses. 4. Allow specific API errors to return more granular failure status for specific operations. An example might be a minion proxy, where the operation that failed may be both proxying OR the minion itself. In this case a reason may be defined "proxy_failed" corresponding to 502, where the Details field may be extended to contain a nested error object. At this time only ID and Kind are exposed
2014-07-31 18:12:26 +00:00
otherStatus := expectApiStatus(t, "GET", fmt.Sprintf("%s/prefix/version/operations/%s", server.URL, status.Details.ID), []byte{}, http.StatusAccepted)
2014-07-31 18:16:13 +00:00
if !reflect.DeepEqual(status, otherStatus) {
t.Errorf("Expected %#v, Got %#v", status, otherStatus)
}
ch <- struct{}{}
time.Sleep(time.Millisecond)
Evolve the api.Status object with Reason/Details Contains breaking API change on api.Status#Details (type change) Turn Details from string -> StatusDetails - a general bucket for keyed error behavior. Define an open enumeration ReasonType exposed as Reason on the status object to provide machine readable subcategorization beyond HTTP Status Code. Define a human readable field Message which is common convention (previously this was joined into Details). Precedence order: HTTP Status Code, Reason, Details. apiserver would impose restraints on the ReasonTypes defined by the main apiobject, and ensure their use is consistent. There are four long term scenarios this change supports: 1. Allow a client access to a machine readable field that can be easily switched on for improving or translating the generic server Message. 2. Return a 404 when a composite operation on multiple resources fails with enough data so that a client can distinguish which item does not exist. E.g. resource Parent and resource Child, POST /parents/1/children to create a new Child, but /parents/1 is deleted. POST returns 404, ReasonTypeNotFound, and Details.ID = "1", Details.Kind = "parent" 3. Allow a client to receive validation data that is keyed by attribute for building user facing UIs around field submission. Validation is usually expressed as map[string][]string, but that type is less appropriate for many other uses. 4. Allow specific API errors to return more granular failure status for specific operations. An example might be a minion proxy, where the operation that failed may be both proxying OR the minion itself. In this case a reason may be defined "proxy_failed" corresponding to 502, where the Details field may be extended to contain a nested error object. At this time only ID and Kind are exposed
2014-07-31 18:12:26 +00:00
finalStatus := expectApiStatus(t, "GET", fmt.Sprintf("%s/prefix/version/operations/%s?after=1", server.URL, status.Details.ID), []byte{}, http.StatusOK)
expectedErr := apierrs.NewAlreadyExists("foo", "bar")
2014-07-31 18:16:13 +00:00
expectedStatus := &api.Status{
Status: api.StatusFailure,
Code: http.StatusConflict,
2014-09-28 04:32:53 +00:00
Reason: "AlreadyExists",
Message: expectedErr.Error(),
Details: &api.StatusDetails{
Kind: "foo",
ID: "bar",
},
2014-07-31 18:16:13 +00:00
}
if !reflect.DeepEqual(expectedStatus, finalStatus) {
t.Errorf("Expected %#v, Got %#v", expectedStatus, finalStatus)
if finalStatus.Details != nil {
t.Logf("Details %#v, Got %#v", *expectedStatus.Details, *finalStatus.Details)
}
2014-07-31 18:16:13 +00:00
}
2014-09-26 00:20:28 +00:00
if !selfLinker.called {
t.Errorf("Never set self link")
}
2014-07-31 18:16:13 +00:00
}
2014-09-08 04:14:18 +00:00
type UnregisteredAPIObject struct {
Value string
}
func (*UnregisteredAPIObject) IsAnAPIObject() {}
2014-07-31 18:16:13 +00:00
func TestWriteJSONDecodeError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
writeJSON(http.StatusOK, codec, &UnregisteredAPIObject{"Undecodable"}, w)
2014-07-31 18:16:13 +00:00
}))
2014-10-31 01:15:44 +00:00
defer server.Close()
status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError)
if status.Reason != api.StatusReasonUnknown {
t.Errorf("unexpected reason %#v", status)
2014-08-04 00:01:15 +00:00
}
2014-09-08 04:14:18 +00:00
if !strings.Contains(status.Message, "type apiserver.UnregisteredAPIObject is not registered") {
t.Errorf("unexpected message %#v", status)
2014-07-31 18:16:13 +00:00
}
}
type marshalError struct {
err error
}
func (m *marshalError) MarshalJSON() ([]byte, error) {
return []byte{}, m.err
}
func TestWriteRAWJSONMarshalError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
writeRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w)
}))
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-07-31 18:16:13 +00:00
client := http.Client{}
resp, err := client.Get(server.URL)
2014-08-04 00:01:15 +00:00
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-07-31 18:16:13 +00:00
if resp.StatusCode != http.StatusInternalServerError {
t.Errorf("unexpected status code %d", resp.StatusCode)
}
}
func TestSyncCreateTimeout(t *testing.T) {
testOver := make(chan struct{})
defer close(testOver)
2014-06-25 20:21:32 +00:00
storage := SimpleRESTStorage{
2014-09-08 04:14:18 +00:00
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
// Eliminate flakes by ensuring the create operation takes longer than this test.
<-testOver
2014-06-25 20:21:32 +00:00
return obj, nil
},
}
handler := Handle(map[string]RESTStorage{
2014-06-25 20:21:32 +00:00
"foo": &storage,
2015-01-06 19:26:17 +00:00
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
2014-10-22 17:02:02 +00:00
simple := &Simple{Other: "foo"}
data, _ := codec.Encode(simple)
2014-07-31 18:16:13 +00:00
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?sync=true&timeout=4ms", data, http.StatusAccepted)
Evolve the api.Status object with Reason/Details Contains breaking API change on api.Status#Details (type change) Turn Details from string -> StatusDetails - a general bucket for keyed error behavior. Define an open enumeration ReasonType exposed as Reason on the status object to provide machine readable subcategorization beyond HTTP Status Code. Define a human readable field Message which is common convention (previously this was joined into Details). Precedence order: HTTP Status Code, Reason, Details. apiserver would impose restraints on the ReasonTypes defined by the main apiobject, and ensure their use is consistent. There are four long term scenarios this change supports: 1. Allow a client access to a machine readable field that can be easily switched on for improving or translating the generic server Message. 2. Return a 404 when a composite operation on multiple resources fails with enough data so that a client can distinguish which item does not exist. E.g. resource Parent and resource Child, POST /parents/1/children to create a new Child, but /parents/1 is deleted. POST returns 404, ReasonTypeNotFound, and Details.ID = "1", Details.Kind = "parent" 3. Allow a client to receive validation data that is keyed by attribute for building user facing UIs around field submission. Validation is usually expressed as map[string][]string, but that type is less appropriate for many other uses. 4. Allow specific API errors to return more granular failure status for specific operations. An example might be a minion proxy, where the operation that failed may be both proxying OR the minion itself. In this case a reason may be defined "proxy_failed" corresponding to 502, where the Details field may be extended to contain a nested error object. At this time only ID and Kind are exposed
2014-07-31 18:12:26 +00:00
if itemOut.Status != api.StatusWorking || itemOut.Details == nil || itemOut.Details.ID == "" {
2014-06-25 20:21:32 +00:00
t.Errorf("Unexpected status %#v", itemOut)
}
}
func TestCORSAllowedOrigins(t *testing.T) {
table := []struct {
allowedOrigins util.StringList
origin string
allowed bool
}{
{[]string{}, "example.com", false},
{[]string{"example.com"}, "example.com", true},
{[]string{"example.com"}, "not-allowed.com", false},
{[]string{"not-matching.com", "example.com"}, "example.com", true},
{[]string{".*"}, "example.com", true},
}
for _, item := range table {
allowedOriginRegexps, err := util.CompileRegexps(item.allowedOrigins)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
2014-09-26 00:20:28 +00:00
handler := CORS(
2015-01-06 19:26:17 +00:00
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl),
2014-09-26 00:20:28 +00:00
allowedOriginRegexps, nil, nil, "true",
)
server := httptest.NewServer(handler)
2014-10-31 01:15:44 +00:00
defer server.Close()
client := http.Client{}
request, err := http.NewRequest("GET", server.URL+"/version", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
request.Header.Set("Origin", item.origin)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if item.allowed {
if !reflect.DeepEqual(item.origin, response.Header.Get("Access-Control-Allow-Origin")) {
t.Errorf("Expected %#v, Got %#v", item.origin, response.Header.Get("Access-Control-Allow-Origin"))
}
if response.Header.Get("Access-Control-Allow-Credentials") == "" {
t.Errorf("Expected Access-Control-Allow-Credentials header to be set")
}
if response.Header.Get("Access-Control-Allow-Headers") == "" {
t.Errorf("Expected Access-Control-Allow-Headers header to be set")
}
if response.Header.Get("Access-Control-Allow-Methods") == "" {
t.Errorf("Expected Access-Control-Allow-Methods header to be set")
}
} else {
if response.Header.Get("Access-Control-Allow-Origin") != "" {
t.Errorf("Expected Access-Control-Allow-Origin header to not be set")
}
if response.Header.Get("Access-Control-Allow-Credentials") != "" {
t.Errorf("Expected Access-Control-Allow-Credentials header to not be set")
}
if response.Header.Get("Access-Control-Allow-Headers") != "" {
t.Errorf("Expected Access-Control-Allow-Headers header to not be set")
}
if response.Header.Get("Access-Control-Allow-Methods") != "" {
t.Errorf("Expected Access-Control-Allow-Methods header to not be set")
}
}
}
}