Add support for installing custom object APIs

This commit is contained in:
Brendan Burns
2015-08-19 22:08:26 -07:00
parent 7bfc8b5f37
commit b196d0f84b
16 changed files with 603 additions and 158 deletions

View File

@@ -17,9 +17,12 @@ limitations under the License.
package master
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
@@ -30,7 +33,7 @@ import (
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
"github.com/emicklei/go-restful"
)
@@ -80,43 +83,281 @@ func TestFindExternalAddress(t *testing.T) {
}
}
func TestInstallThirdPartyAPI(t *testing.T) {
type Foo struct {
api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"`
SomeField string `json:"someField"`
OtherField int `json:"otherField"`
}
type FooList struct {
api.TypeMeta `json:",inline"`
api.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"`
items []Foo `json:"items"`
}
func initThirdParty(t *testing.T) (*tools.FakeEtcdClient, *httptest.Server) {
master := &Master{}
api := &expapi.ThirdPartyResource{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Name: "foo.company.com",
},
Versions: []expapi.APIVersion{
expapi.APIVersion{
{
APIGroup: "group",
Name: "v1",
Name: "v1",
},
},
}
master.handlerContainer = restful.NewContainer()
fakeClient := tools.NewFakeEtcdClient(t)
fakeClient.Machines = []string{"http://machine1:4001", "http://machine2", "http://machine3:4003"}
master.thirdPartyStorage = etcdstorage.NewEtcdStorage(fakeClient, explatest.Codec, etcdtest.PathPrefix())
if err := master.InstallThirdPartyAPI(api); err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
server := httptest.NewServer(master.handlerContainer.ServeMux)
return fakeClient, server
}
func TestInstallThirdPartyAPIList(t *testing.T) {
fakeClient, server := initThirdParty(t)
defer server.Close()
resp, err := http.Get(server.URL + "/thirdparty/foo/v1")
fakeClient.ExpectNotFoundGet(etcdtest.PathPrefix() + "/ThirdPartyResourceData/company.com/foos/default")
resp, err := http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected status: %v", resp)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if string(data) != "foo" {
t.Errorf("unexpected response: %s", string(data))
list := FooList{}
if err := json.Unmarshal(data, &list); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func encodeToThirdParty(name string, obj interface{}) ([]byte, error) {
serial, err := json.Marshal(obj)
if err != nil {
return nil, err
}
thirdPartyData := expapi.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{Name: name},
Data: serial,
}
return latest.Codec.Encode(&thirdPartyData)
}
func storeToEtcd(fakeClient *tools.FakeEtcdClient, path, name string, obj interface{}) error {
data, err := encodeToThirdParty(name, obj)
if err != nil {
return err
}
_, err = fakeClient.Set(etcdtest.PathPrefix()+path, string(data), 0)
return err
}
func decodeResponse(resp *http.Response, obj interface{}) error {
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.Unmarshal(data, obj); err != nil {
return err
}
return nil
}
func TestInstallThirdPartyAPIGet(t *testing.T) {
fakeClient, server := initThirdParty(t)
defer server.Close()
expectedObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: api.TypeMeta{
Kind: "foo",
},
SomeField: "test field",
OtherField: 10,
}
if err := storeToEtcd(fakeClient, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj); err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
return
}
resp, err := http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected status: %v", resp)
}
item := Foo{}
if err := decodeResponse(resp, &item); err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(item, expectedObj) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item)
}
}
func TestInstallThirdPartyAPIPost(t *testing.T) {
fakeClient, server := initThirdParty(t)
defer server.Close()
inputObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: api.TypeMeta{
Kind: "foo",
},
SomeField: "test field",
OtherField: 10,
}
data, err := encodeToThirdParty("test", inputObj)
if err != nil {
t.Errorf("unexpected error: %v")
return
}
resp, err := http.Post(server.URL+"/thirdparty/company.com/v1/namespaces/default/foos", "application/json", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if resp.StatusCode != http.StatusCreated {
t.Errorf("unexpected status: %v", resp)
}
item := Foo{}
if err := decodeResponse(resp, &item); err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(item, inputObj) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", inputObj, item)
}
etcdResp, err := fakeClient.Get(etcdtest.PathPrefix()+"/ThirdPartyResourceData/company.com/foos/default/test", false, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
obj, err := explatest.Codec.Decode([]byte(etcdResp.Node.Value))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
thirdPartyObj, ok := obj.(*expapi.ThirdPartyResourceData)
if !ok {
t.Errorf("unexpected object: %v", obj)
}
item = Foo{}
if err := json.Unmarshal(thirdPartyObj.Data, &item); err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(item, inputObj) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", inputObj, item)
}
}
func TestInstallThirdPartyAPIDelete(t *testing.T) {
fakeClient, server := initThirdParty(t)
defer server.Close()
expectedObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: api.TypeMeta{
Kind: "foo",
},
SomeField: "test field",
OtherField: 10,
}
if err := storeToEtcd(fakeClient, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj); err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
return
}
resp, err := http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected status: %v", resp)
}
item := Foo{}
if err := decodeResponse(resp, &item); err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(item, expectedObj) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item)
}
resp, err = httpDelete(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected status: %v", resp)
}
resp, err = http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if resp.StatusCode != http.StatusNotFound {
t.Errorf("unexpected status: %v", resp)
}
expectDeletedKeys := []string{etcdtest.PathPrefix() + "/ThirdPartyResourceData/company.com/foos/default/test"}
if !reflect.DeepEqual(fakeClient.DeletedKeys, expectDeletedKeys) {
t.Errorf("unexpected deleted keys: %v", fakeClient.DeletedKeys)
}
}
func httpDelete(url string) (*http.Response, error) {
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return nil, err
}
client := &http.Client{}
return client.Do(req)
}