k3s/pkg/apiserver/apiserver_test.go

1044 lines
31 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
// "internal" version
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{},
&api.Status{})
// "version" version
// TODO: Use versioned api objects?
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{},
&api.Status{})
defMapper := meta.NewDefaultRESTMapper(
versions,
func(version string) (*meta.VersionInterfaces, bool) {
interfaces, err := interfacesFor(version)
if err != nil {
return nil, false
}
return interfaces, true
},
)
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources
for _, version := range versions {
for kind := range api.Scheme.KnownTypes(version) {
mixedCase := true
scope := meta.RESTScopeNamespaceLegacy
defMapper.Add(scope, kind, version, mixedCase)
}
}
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) NewList() runtime.Object {
return &SimpleList{}
}
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{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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)
t.Errorf("MAPPER: %v", mapper)
2014-07-31 18:16:13 +00:00
}
}
}
type UnimplementedRESTStorage struct{}
func (UnimplementedRESTStorage) New() runtime.Object {
return &Simple{}
}
func TestMethodNotAllowed(t *testing.T) {
type T struct {
Method string
Path string
}
cases := map[string]T{
"GET object": {"GET", "/prefix/version/foo/bar"},
"GET list": {"GET", "/prefix/version/foo"},
"POST list": {"POST", "/prefix/version/foo"},
"PUT object": {"PUT", "/prefix/version/foo/bar"},
"DELETE object": {"DELETE", "/prefix/version/foo/bar"},
//"watch list": {"GET", "/prefix/version/watch/foo"},
//"watch object": {"GET", "/prefix/version/watch/foo/bar"},
"proxy object": {"GET", "/prefix/version/proxy/foo/bar"},
"redirect object": {"GET", "/prefix/version/redirect/foo/bar"},
}
handler := Handle(map[string]RESTStorage{
"foo": UnimplementedRESTStorage{},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
for k, v := range cases {
request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`)))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
response, err := client.Do(request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
continue
}
defer response.Body.Close()
data, _ := ioutil.ReadAll(response.Body)
t.Logf("resp: %s", string(data))
if response.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("%s: expected %d for %s, Got %s", k, http.StatusMethodNotAllowed, v.Method, string(data))
continue
}
obj, err := codec.Decode(data)
if err != nil {
t.Errorf("%s: unexpected decode error: %v", k, err)
continue
}
status, ok := obj.(*api.Status)
if !ok {
t.Errorf("%s: unexpected object: %#v", k, obj)
continue
}
if status.Reason != api.StatusReasonMethodNotAllowed {
t.Errorf("%s: unexpected status: %#v", k, status)
}
}
}
2014-07-31 18:16:13 +00:00
func TestVersion(t *testing.T) {
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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(), mapper)
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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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,
}
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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(), 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.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
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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)
}
}
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
},
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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
}
2015-01-22 05:20:57 +00:00
func TestCreate(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/foo/bar?namespace=other",
2014-09-26 00:20:28 +00:00
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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/foo?namespace=other", 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")
}
}
2015-01-22 05:20:57 +00:00
func TestCreateInvokesAdmissionControl(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: "other",
expectedSet: "/prefix/version/foo/bar?namespace=other",
2015-01-22 05:20:57 +00:00
}
handler := Handle(map[string]RESTStorage{
"foo": &storage,
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
2015-01-22 05:20:57 +00:00
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?namespace=other", bytes.NewBuffer(data))
2015-01-22 05:20:57 +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()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response)
}
}
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
}
2015-01-22 05:20:57 +00:00
func TestDelayReturnsError(t *testing.T) {
2014-07-31 18:16:13 +00:00
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
},
}
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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)
}
}
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)
}
}
2015-01-22 05:20:57 +00:00
func TestCreateTimeout(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,
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
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)
2015-01-22 05:20:57 +00:00
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?timeout=4ms", data, http.StatusAccepted)
if itemOut.Status != api.StatusFailure || itemOut.Reason != api.StatusReasonTimeout {
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(
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper),
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")
}
}
}
}