Add dynamic APIs

This commit is contained in:
Brendan Burns 2015-08-19 11:02:01 -07:00
parent 828e4d35a7
commit 7bfc8b5f37
14 changed files with 747 additions and 0 deletions

View File

@ -1005,6 +1005,44 @@ func deepCopy_expapi_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyRe
return nil
}
func deepCopy_expapi_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error {
if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := deepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
return err
}
if in.Data != nil {
out.Data = make([]uint8, len(in.Data))
for i := range in.Data {
out.Data[i] = in.Data[i]
}
} else {
out.Data = nil
}
return nil
}
func deepCopy_expapi_ThirdPartyResourceDataList(in ThirdPartyResourceDataList, out *ThirdPartyResourceDataList, c *conversion.Cloner) error {
if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := deepCopy_api_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
return err
}
if in.Items != nil {
out.Items = make([]ThirdPartyResourceData, len(in.Items))
for i := range in.Items {
if err := deepCopy_expapi_ThirdPartyResourceData(in.Items[i], &out.Items[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func deepCopy_expapi_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error {
if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
@ -1101,6 +1139,8 @@ func init() {
deepCopy_expapi_ScaleStatus,
deepCopy_expapi_SubresourceReference,
deepCopy_expapi_ThirdPartyResource,
deepCopy_expapi_ThirdPartyResourceData,
deepCopy_expapi_ThirdPartyResourceDataList,
deepCopy_expapi_ThirdPartyResourceList,
deepCopy_util_IntOrString,
deepCopy_util_Time,

View File

@ -38,6 +38,8 @@ func addKnownTypes() {
&ThirdPartyResourceList{},
&DaemonList{},
&Daemon{},
&ThirdPartyResourceData{},
&ThirdPartyResourceDataList{},
)
}
@ -51,3 +53,5 @@ func (*ThirdPartyResource) IsAnAPIObject() {}
func (*ThirdPartyResourceList) IsAnAPIObject() {}
func (*Daemon) IsAnAPIObject() {}
func (*DaemonList) IsAnAPIObject() {}
func (*ThirdPartyResourceData) IsAnAPIObject() {}
func (*ThirdPartyResourceDataList) IsAnAPIObject() {}

View File

@ -167,6 +167,7 @@ type ThirdPartyResourceData struct {
Data []byte `json:"name,omitempty" description:"the raw JSON data for this data"`
}
<<<<<<< HEAD
type Deployment struct {
api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata,omitempty"`
@ -271,6 +272,7 @@ type DeploymentList struct {
Items []Deployment `json:"items"`
}
<<<<<<< HEAD
// DaemonSpec is the specification of a daemon.
type DaemonSpec struct {
// Selector is a label query over pods that are managed by the daemon.
@ -318,3 +320,10 @@ type DaemonList struct {
Items []Daemon `json:"items"`
}
type ThirdPartyResourceDataList struct {
api.TypeMeta `json:",inline"`
api.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"`
Items []ThirdPartyResourceData `json:"items" description:"items is a list of third party objects"`
}

View File

@ -1742,6 +1742,45 @@ func convert_expapi_ThirdPartyResource_To_v1_ThirdPartyResource(in *expapi.Third
return nil
}
func convert_expapi_ThirdPartyResourceData_To_v1_ThirdPartyResourceData(in *expapi.ThirdPartyResourceData, out *ThirdPartyResourceData, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*expapi.ThirdPartyResourceData))(in)
}
if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
if err := convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil {
return err
}
if err := s.Convert(&in.Data, &out.Data, 0); err != nil {
return err
}
return nil
}
func convert_expapi_ThirdPartyResourceDataList_To_v1_ThirdPartyResourceDataList(in *expapi.ThirdPartyResourceDataList, out *ThirdPartyResourceDataList, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*expapi.ThirdPartyResourceDataList))(in)
}
if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
if err := convert_api_ListMeta_To_v1_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil {
return err
}
if in.Items != nil {
out.Items = make([]ThirdPartyResourceData, len(in.Items))
for i := range in.Items {
if err := convert_expapi_ThirdPartyResourceData_To_v1_ThirdPartyResourceData(&in.Items[i], &out.Items[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func convert_expapi_ThirdPartyResourceList_To_v1_ThirdPartyResourceList(in *expapi.ThirdPartyResourceList, out *ThirdPartyResourceList, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*expapi.ThirdPartyResourceList))(in)
@ -2105,6 +2144,45 @@ func convert_v1_ThirdPartyResource_To_expapi_ThirdPartyResource(in *ThirdPartyRe
return nil
}
func convert_v1_ThirdPartyResourceData_To_expapi_ThirdPartyResourceData(in *ThirdPartyResourceData, out *expapi.ThirdPartyResourceData, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ThirdPartyResourceData))(in)
}
if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
if err := convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil {
return err
}
if err := s.Convert(&in.Data, &out.Data, 0); err != nil {
return err
}
return nil
}
func convert_v1_ThirdPartyResourceDataList_To_expapi_ThirdPartyResourceDataList(in *ThirdPartyResourceDataList, out *expapi.ThirdPartyResourceDataList, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ThirdPartyResourceDataList))(in)
}
if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
if err := convert_v1_ListMeta_To_api_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil {
return err
}
if in.Items != nil {
out.Items = make([]expapi.ThirdPartyResourceData, len(in.Items))
for i := range in.Items {
if err := convert_v1_ThirdPartyResourceData_To_expapi_ThirdPartyResourceData(&in.Items[i], &out.Items[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func convert_v1_ThirdPartyResourceList_To_expapi_ThirdPartyResourceList(in *ThirdPartyResourceList, out *expapi.ThirdPartyResourceList, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ThirdPartyResourceList))(in)
@ -2184,6 +2262,8 @@ func init() {
convert_expapi_ScaleStatus_To_v1_ScaleStatus,
convert_expapi_Scale_To_v1_Scale,
convert_expapi_SubresourceReference_To_v1_SubresourceReference,
convert_expapi_ThirdPartyResourceDataList_To_v1_ThirdPartyResourceDataList,
convert_expapi_ThirdPartyResourceData_To_v1_ThirdPartyResourceData,
convert_expapi_ThirdPartyResourceList_To_v1_ThirdPartyResourceList,
convert_expapi_ThirdPartyResource_To_v1_ThirdPartyResource,
convert_v1_APIVersion_To_expapi_APIVersion,
@ -2235,6 +2315,8 @@ func init() {
convert_v1_SecurityContext_To_api_SecurityContext,
convert_v1_SubresourceReference_To_expapi_SubresourceReference,
convert_v1_TCPSocketAction_To_api_TCPSocketAction,
convert_v1_ThirdPartyResourceDataList_To_expapi_ThirdPartyResourceDataList,
convert_v1_ThirdPartyResourceData_To_expapi_ThirdPartyResourceData,
convert_v1_ThirdPartyResourceList_To_expapi_ThirdPartyResourceList,
convert_v1_ThirdPartyResource_To_expapi_ThirdPartyResource,
convert_v1_TypeMeta_To_api_TypeMeta,

View File

@ -1012,6 +1012,44 @@ func deepCopy_v1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResour
return nil
}
func deepCopy_v1_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error {
if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
return err
}
if in.Data != nil {
out.Data = make([]uint8, len(in.Data))
for i := range in.Data {
out.Data[i] = in.Data[i]
}
} else {
out.Data = nil
}
return nil
}
func deepCopy_v1_ThirdPartyResourceDataList(in ThirdPartyResourceDataList, out *ThirdPartyResourceDataList, c *conversion.Cloner) error {
if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := deepCopy_v1_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
return err
}
if in.Items != nil {
out.Items = make([]ThirdPartyResourceData, len(in.Items))
for i := range in.Items {
if err := deepCopy_v1_ThirdPartyResourceData(in.Items[i], &out.Items[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func deepCopy_v1_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error {
if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
@ -1108,6 +1146,8 @@ func init() {
deepCopy_v1_ScaleStatus,
deepCopy_v1_SubresourceReference,
deepCopy_v1_ThirdPartyResource,
deepCopy_v1_ThirdPartyResourceData,
deepCopy_v1_ThirdPartyResourceDataList,
deepCopy_v1_ThirdPartyResourceList,
deepCopy_util_IntOrString,
deepCopy_util_Time,

View File

@ -42,6 +42,8 @@ func addKnownTypes() {
&ThirdPartyResourceList{},
&DaemonList{},
&Daemon{},
&ThirdPartyResourceData{},
&ThirdPartyResourceDataList{},
)
}
@ -55,3 +57,5 @@ func (*ThirdPartyResource) IsAnAPIObject() {}
func (*ThirdPartyResourceList) IsAnAPIObject() {}
func (*Daemon) IsAnAPIObject() {}
func (*DaemonList) IsAnAPIObject() {}
func (*ThirdPartyResourceData) IsAnAPIObject() {}
func (*ThirdPartyResourceDataList) IsAnAPIObject() {}

View File

@ -153,6 +153,7 @@ type ThirdPartyResourceData struct {
Data []byte `json:"name,omitempty" description:"the raw JSON data for this data"`
}
<<<<<<< HEAD
type Deployment struct {
v1.TypeMeta `json:",inline"`
v1.ObjectMeta `json:"metadata,omitempty"`
@ -258,6 +259,7 @@ type DeploymentList struct {
Items []Deployment `json:"items" description:"list of deployments"`
}
<<<<<<< HEAD
// DaemonSpec is the specification of a daemon.
type DaemonSpec struct {
// Selector is a label query over pods that are managed by the daemon.
@ -318,3 +320,10 @@ type DaemonList struct {
// Items is a list of daemons.
Items []Daemon `json:"items"`
}
type ThirdPartyResourceDataList struct {
v1.TypeMeta `json:",inline"`
v1.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"`
Items []ThirdPartyResourceData `json:"items" description:"items is a list of third party objects"`
}

View File

@ -42,6 +42,7 @@ import (
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/handlers"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/expapi"
explatest "k8s.io/kubernetes/pkg/expapi/latest"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/healthz"
@ -71,6 +72,7 @@ import (
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd"
thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd"
thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools"
@ -233,6 +235,9 @@ type Master struct {
lastSync int64 // Seconds since Epoch
lastSyncMetric prometheus.GaugeFunc
clock util.Clock
// storage for third party objects
thirdPartyStorage storage.Interface
}
// NewEtcdStorage returns a storage.Interface for the provided arguments or an error if the version
@ -765,6 +770,50 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion {
return version
}
func (m *Master) InstallThirdPartyAPI(rsrc *expapi.ThirdPartyResource) error {
thirdparty := m.thirdpartyapi(rsrc)
if err := thirdparty.InstallREST(m.handlerContainer); err != nil {
glog.Fatalf("Unable to setup thirdparty api: %v", err)
}
thirdPartyPrefix := "/thirdparty/" + rsrc.Name + "/"
apiRoot := rsrc.Name
apiserver.AddApiWebService(m.handlerContainer, thirdPartyPrefix, []string{rsrc.Versions[0].Name})
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: util.NewStringSet(strings.TrimPrefix(apiRoot, "/")), RestMapper: thirdparty.Mapper}
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
return nil
}
func (m *Master) thirdpartyapi(rsrc *expapi.ThirdPartyResource) *apiserver.APIGroupVersion {
resourceStorage := thirdpartyresourcedataetcd.NewREST(m.thirdPartyStorage)
apiGroup := rsrc.Name
apiRoot := "/thirdparty/" + rsrc.Name + "/" + rsrc.Versions[0].Name
storage := map[string]rest.Storage{
apiGroup: resourceStorage,
}
return &apiserver.APIGroupVersion{
Root: apiRoot,
Creater: api.Scheme,
Convertor: api.Scheme,
Typer: api.Scheme,
Mapper: explatest.RESTMapper,
Codec: explatest.Codec,
Linker: explatest.SelfLinker,
Storage: storage,
Version: rsrc.Versions[0].Name,
Admit: m.admissionControl,
Context: m.requestContextMapper,
ProxyDialerFn: m.dialer,
MinRequestTimeout: m.minRequestTimeout,
}
}
// expapi returns the resources and codec for the experimental api
func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion {
controllerStorage := expcontrolleretcd.NewStorage(c.ExpDatabaseStorage)

View File

@ -17,15 +17,21 @@ limitations under the License.
package master
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/expapi"
explatest "k8s.io/kubernetes/pkg/expapi/latest"
"k8s.io/kubernetes/pkg/registry/registrytest"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
"github.com/emicklei/go-restful"
)
func TestGetServersToValidate(t *testing.T) {
@ -73,3 +79,44 @@ func TestFindExternalAddress(t *testing.T) {
t.Errorf("expected findExternalAddress to fail on a node with missing ip information")
}
}
func TestInstallThirdPartyAPI(t *testing.T) {
master := &Master{}
api := &expapi.ThirdPartyResource{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Versions: []expapi.APIVersion{
expapi.APIVersion{
APIGroup: "group",
Name: "v1",
},
},
}
master.handlerContainer = restful.NewContainer()
if err := master.InstallThirdPartyAPI(api); err != nil {
t.Errorf("unexpected error: %v", err)
}
server := httptest.NewServer(master.handlerContainer.ServeMux)
defer server.Close()
resp, err := http.Get(server.URL + "/thirdparty/foo/v1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
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))
}
}

View File

@ -0,0 +1,19 @@
/*
Copyright 2015 The Kubernetes Authors 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.
*/
// Package thirdpartyresourcedata provides Registry interface and its REST
// implementation for storing ThirdPartyResourceData api objects.
package thirdpartyresourcedata

View File

@ -0,0 +1,63 @@
/*
Copyright 2015 The Kubernetes Authors 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.
*/
package etcd
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
)
// REST implements a RESTStorage for ThirdPartyResourceDatas against etcd
type REST struct {
*etcdgeneric.Etcd
}
// NewREST returns a registry which will store ThirdPartyResourceData in the given helper
func NewREST(s storage.Interface) *REST {
prefix := "/ThirdPartyResourceData"
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &expapi.ThirdPartyResourceData{} },
NewListFunc: func() runtime.Object { return &expapi.ThirdPartyResourceDataList{} },
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
},
KeyFunc: func(ctx api.Context, id string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id)
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*expapi.ThirdPartyResourceData).Name, nil
},
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return thirdpartyresourcedata.Matcher(label, field)
},
EndpointName: "thirdpartyresourcedata",
CreateStrategy: thirdpartyresourcedata.Strategy,
UpdateStrategy: thirdpartyresourcedata.Strategy,
Storage: s,
}
return &REST{store}
}

View File

@ -0,0 +1,205 @@
/*
Copyright 2015 The Kubernetes Authors 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.
*/
package etcd
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest/resttest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/expapi/v1"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
"github.com/coreos/go-etcd/etcd"
)
var scheme *runtime.Scheme
var codec runtime.Codec
func init() {
// Ensure that expapi/v1 packege is used, so that it will get initialized and register HorizontalPodAutoscaler object.
_ = v1.ThirdPartyResource{}
}
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, storage.Interface) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
storage := NewREST(etcdStorage)
return storage, fakeEtcdClient, etcdStorage
}
func validNewThirdPartyResource(name string) *expapi.ThirdPartyResource {
return &expapi.ThirdPartyResource{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
},
Versions: []expapi.APIVersion{
{
Name: "stable/v1",
},
},
}
}
func TestCreate(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
rsrc := validNewThirdPartyResource("foo")
rsrc.ObjectMeta = api.ObjectMeta{}
test.TestCreate(
// valid
rsrc,
// invalid
&expapi.ThirdPartyResource{},
)
}
func TestUpdate(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
key, err := storage.KeyFunc(test.TestContext(), "foo")
if err != nil {
t.Fatal(err)
}
key = etcdtest.AddPrefix(key)
fakeEtcdClient.ExpectNotFoundGet(key)
fakeEtcdClient.ChangeIndex = 2
rsrc := validNewThirdPartyResource("foo")
existing := validNewThirdPartyResource("exists")
existing.Namespace = test.TestNamespace()
obj, err := storage.Create(test.TestContext(), existing)
if err != nil {
t.Fatalf("unable to create object: %v", err)
}
older := obj.(*expapi.ThirdPartyResource)
older.ResourceVersion = "1"
test.TestUpdate(
rsrc,
existing,
older,
)
}
func TestDelete(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeEtcdClient, _ := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
rsrc := validNewThirdPartyResource("foo2")
key, _ := storage.KeyFunc(ctx, "foo2")
key = etcdtest.AddPrefix(key)
createFn := func() runtime.Object {
fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), rsrc),
ModifiedIndex: 1,
},
},
}
return rsrc
}
gracefulSetFn := func() bool {
if fakeEtcdClient.Data[key].R.Node == nil {
return false
}
return fakeEtcdClient.Data[key].R.Node.TTL == 30
}
test.TestDeleteNoGraceful(createFn, gracefulSetFn)
}
func TestGet(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
rsrc := validNewThirdPartyResource("foo")
test.TestGet(rsrc)
}
func TestEmptyList(t *testing.T) {
ctx := api.NewDefaultContext()
registry, fakeClient, _ := newStorage(t)
fakeClient.ChangeIndex = 1
key := registry.KeyRootFunc(ctx)
key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{},
E: fakeClient.NewError(tools.EtcdErrorCodeNotFound),
}
rsrcList, err := registry.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(rsrcList.(*expapi.ThirdPartyResourceList).Items) != 0 {
t.Errorf("Unexpected non-zero autoscaler list: %#v", rsrcList)
}
if rsrcList.(*expapi.ThirdPartyResourceList).ResourceVersion != "1" {
t.Errorf("Unexpected resource version: %#v", rsrcList)
}
}
func TestList(t *testing.T) {
ctx := api.NewDefaultContext()
registry, fakeClient, _ := newStorage(t)
fakeClient.ChangeIndex = 1
key := registry.KeyRootFunc(ctx)
key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResource{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}),
},
{
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResource{
ObjectMeta: api.ObjectMeta{Name: "bar"},
}),
},
},
},
},
}
obj, err := registry.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
rsrcList := obj.(*expapi.ThirdPartyResourceList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(rsrcList.Items) != 2 {
t.Errorf("Unexpected ThirdPartyResource list: %#v", rsrcList)
}
if rsrcList.Items[0].Name != "foo" {
t.Errorf("Unexpected ThirdPartyResource: %#v", rsrcList.Items[0])
}
if rsrcList.Items[1].Name != "bar" {
t.Errorf("Unexpected ThirdPartyResource: %#v", rsrcList.Items[1])
}
}

View File

@ -0,0 +1,88 @@
/*
Copyright 2015 The Kubernetes Authors 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.
*/
package thirdpartyresourcedata
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/watch"
)
// Registry is an interface implemented by things that know how to store ThirdPartyResourceData objects.
type Registry interface {
// ListThirdPartyResourceData obtains a list of ThirdPartyResourceData having labels which match selector.
ListThirdPartyResourceData(ctx api.Context, selector labels.Selector) (*expapi.ThirdPartyResourceDataList, error)
// Watch for new/changed/deleted ThirdPartyResourceData
WatchThirdPartyResourceData(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
// Get a specific ThirdPartyResourceData
GetThirdPartyResourceData(ctx api.Context, name string) (*expapi.ThirdPartyResourceData, error)
// Create a ThirdPartyResourceData based on a specification.
CreateThirdPartyResourceData(ctx api.Context, resource *expapi.ThirdPartyResourceData) (*expapi.ThirdPartyResourceData, error)
// Update an existing ThirdPartyResourceData
UpdateThirdPartyResourceData(ctx api.Context, resource *expapi.ThirdPartyResourceData) (*expapi.ThirdPartyResourceData, error)
// Delete an existing ThirdPartyResourceData
DeleteThirdPartyResourceData(ctx api.Context, name string) error
}
// storage puts strong typing around storage calls
type storage struct {
rest.StandardStorage
}
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
// types will panic.
func NewRegistry(s rest.StandardStorage) Registry {
return &storage{s}
}
func (s *storage) ListThirdPartyResourceData(ctx api.Context, label labels.Selector) (*expapi.ThirdPartyResourceDataList, error) {
obj, err := s.List(ctx, label, fields.Everything())
if err != nil {
return nil, err
}
return obj.(*expapi.ThirdPartyResourceDataList), nil
}
func (s *storage) WatchThirdPartyResourceData(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return s.Watch(ctx, label, field, resourceVersion)
}
func (s *storage) GetThirdPartyResourceData(ctx api.Context, name string) (*expapi.ThirdPartyResourceData, error) {
obj, err := s.Get(ctx, name)
if err != nil {
return nil, err
}
return obj.(*expapi.ThirdPartyResourceData), nil
}
func (s *storage) CreateThirdPartyResourceData(ctx api.Context, ThirdPartyResourceData *expapi.ThirdPartyResourceData) (*expapi.ThirdPartyResourceData, error) {
obj, err := s.Create(ctx, ThirdPartyResourceData)
return obj.(*expapi.ThirdPartyResourceData), err
}
func (s *storage) UpdateThirdPartyResourceData(ctx api.Context, ThirdPartyResourceData *expapi.ThirdPartyResourceData) (*expapi.ThirdPartyResourceData, error) {
obj, _, err := s.Update(ctx, ThirdPartyResourceData)
return obj.(*expapi.ThirdPartyResourceData), err
}
func (s *storage) DeleteThirdPartyResourceData(ctx api.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -0,0 +1,88 @@
/*
Copyright 2015 The Kubernetes Authors 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.
*/
package thirdpartyresourcedata
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/expapi/validation"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/fielderrors"
)
// strategy implements behavior for ThirdPartyResource objects
type strategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Strategy is the default logic that applies when creating and updating ThirdPartyResource
// objects via the REST API.
var Strategy = strategy{api.Scheme, api.SimpleNameGenerator}
var _ = rest.RESTCreateStrategy(Strategy)
var _ = rest.RESTUpdateStrategy(Strategy)
func (strategy) NamespaceScoped() bool {
return true
}
func (strategy) PrepareForCreate(obj runtime.Object) {
}
func (strategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateThirdPartyResource(obj.(*expapi.ThirdPartyResource))
}
func (strategy) AllowCreateOnUpdate() bool {
return false
}
func (strategy) PrepareForUpdate(obj, old runtime.Object) {
}
func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateThirdPartyResourceUpdate(old.(*expapi.ThirdPartyResource), obj.(*expapi.ThirdPartyResource))
}
func (strategy) AllowUnconditionalUpdate() bool {
return true
}
// Matcher returns a generic matcher for a given label and field selector.
func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
sa, ok := obj.(*expapi.ThirdPartyResource)
if !ok {
return false, fmt.Errorf("not a ThirdPartyResource")
}
fields := SelectableFields(sa)
return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil
})
}
// SelectableFields returns a label set that can be used for filter selection
func SelectableFields(obj *expapi.ThirdPartyResource) labels.Set {
return labels.Set{}
}