Merge pull request #13000 from brendandburns/schema-api-2

Add support for dynamic APIs
pull/6/head
Abhi Shah 2015-09-02 13:53:28 -07:00
commit 1b0cf281e5
25 changed files with 1522 additions and 75 deletions

View File

@ -2050,24 +2050,6 @@ func deepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *c
return nil
}
func deepCopy_api_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_api_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
@ -2354,7 +2336,6 @@ func init() {
deepCopy_api_StatusCause,
deepCopy_api_StatusDetails,
deepCopy_api_TCPSocketAction,
deepCopy_api_ThirdPartyResourceData,
deepCopy_api_TypeMeta,
deepCopy_api_Volume,
deepCopy_api_VolumeMount,

View File

@ -2279,22 +2279,6 @@ func convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction,
return nil
}
func convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData(in *api.ThirdPartyResourceData, out *ThirdPartyResourceData, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.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_api_TypeMeta_To_v1_TypeMeta(in *api.TypeMeta, out *TypeMeta, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.TypeMeta))(in)
@ -4697,22 +4681,6 @@ func convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out
return nil
}
func convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData(in *ThirdPartyResourceData, out *api.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_TypeMeta_To_api_TypeMeta(in *TypeMeta, out *api.TypeMeta, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*TypeMeta))(in)
@ -4977,7 +4945,6 @@ func init() {
convert_api_StatusDetails_To_v1_StatusDetails,
convert_api_Status_To_v1_Status,
convert_api_TCPSocketAction_To_v1_TCPSocketAction,
convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData,
convert_api_TypeMeta_To_v1_TypeMeta,
convert_api_VolumeMount_To_v1_VolumeMount,
convert_api_VolumeSource_To_v1_VolumeSource,
@ -5095,7 +5062,6 @@ func init() {
convert_v1_StatusDetails_To_api_StatusDetails,
convert_v1_Status_To_api_Status,
convert_v1_TCPSocketAction_To_api_TCPSocketAction,
convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData,
convert_v1_TypeMeta_To_api_TypeMeta,
convert_v1_VolumeMount_To_api_VolumeMount,
convert_v1_VolumeSource_To_api_VolumeSource,

View File

@ -2055,24 +2055,6 @@ func deepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *co
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_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
@ -2356,7 +2338,6 @@ func init() {
deepCopy_v1_StatusCause,
deepCopy_v1_StatusDetails,
deepCopy_v1_TCPSocketAction,
deepCopy_v1_ThirdPartyResourceData,
deepCopy_v1_TypeMeta,
deepCopy_v1_Volume,
deepCopy_v1_VolumeMount,

View File

@ -25,6 +25,11 @@ import (
func addDefaultingFuncs() {
api.Scheme.AddDefaultingFuncs(
func(obj *APIVersion) {
if len(obj.APIGroup) == 0 {
obj.APIGroup = "experimental"
}
},
func(obj *ReplicationController) {
var labels map[string]string
if obj.Spec.Template != nil {

View File

@ -248,6 +248,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
var ctxFn ContextFunc
ctxFn = func(req *restful.Request) api.Context {
if context == nil {
return api.NewContext()
}
if ctx, ok := context.Get(req.Request); ok {
return ctx
}

View File

@ -309,7 +309,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
return
}
if admit.Handles(admission.Create) {
if admit != nil && admit.Handles(admission.Create) {
userInfo, _ := api.UserFrom(ctx)
err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo))
@ -481,7 +481,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
return
}
if admit.Handles(admission.Update) {
if admit != nil && admit.Handles(admission.Update) {
userInfo, _ := api.UserFrom(ctx)
err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
@ -546,7 +546,7 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope,
}
}
if admit.Handles(admission.Delete) {
if admit != nil && admit.Handles(admission.Delete) {
userInfo, _ := api.UserFrom(ctx)
err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo))

View File

@ -1076,6 +1076,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
@ -1176,6 +1214,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

@ -318,3 +318,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

@ -1888,6 +1888,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)
@ -2237,6 +2276,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)
@ -2318,6 +2396,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,
@ -2372,6 +2452,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

@ -1098,6 +1098,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
@ -1198,6 +1236,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

@ -318,3 +318,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

@ -255,3 +255,15 @@ func ValidateDeployment(obj *expapi.Deployment) errs.ValidationErrorList {
allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec).Prefix("spec")...)
return allErrs
}
func ValidateThirdPartyResourceDataUpdate(old, update *expapi.ThirdPartyResourceData) errs.ValidationErrorList {
return ValidateThirdPartyResourceData(update)
}
func ValidateThirdPartyResourceData(obj *expapi.ThirdPartyResourceData) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if len(obj.Name) == 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("name", obj.Name, "name must be non-empty"))
}
return allErrs
}

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,8 @@ 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"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
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 +236,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 +771,52 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion {
return version
}
func (m *Master) InstallThirdPartyAPI(rsrc *expapi.ThirdPartyResource) error {
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
if err != nil {
return err
}
thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name)
if err := thirdparty.InstallREST(m.handlerContainer); err != nil {
glog.Fatalf("Unable to setup thirdparty api: %v", err)
}
thirdPartyPrefix := "/thirdparty/" + group + "/"
apiserver.AddApiWebService(m.handlerContainer, thirdPartyPrefix, []string{rsrc.Versions[0].Name})
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: util.NewStringSet(strings.TrimPrefix(group, "/")), RestMapper: thirdparty.Mapper}
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
return nil
}
func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupVersion {
resourceStorage := thirdpartyresourcedataetcd.NewREST(m.thirdPartyStorage, group, kind)
apiRoot := "/thirdparty/" + group + "/"
storage := map[string]rest.Storage{
strings.ToLower(kind) + "s": resourceStorage,
}
return &apiserver.APIGroupVersion{
Root: apiRoot,
Creater: thirdpartyresourcedata.NewObjectCreator(version, api.Scheme),
Convertor: api.Scheme,
Typer: api.Scheme,
Mapper: thirdpartyresourcedata.NewMapper(explatest.RESTMapper, kind, version),
Codec: explatest.Codec,
Linker: explatest.SelfLinker,
Storage: storage,
Version: version,
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,24 @@ limitations under the License.
package master
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"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 +82,311 @@ func TestFindExternalAddress(t *testing.T) {
t.Errorf("expected findExternalAddress to fail on a node with missing ip information")
}
}
var versionsToTest = []string{"v1", "v3"}
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, version string) (*tools.FakeEtcdClient, *httptest.Server) {
master := &Master{}
api := &expapi.ThirdPartyResource{
ObjectMeta: api.ObjectMeta{
Name: "foo.company.com",
},
Versions: []expapi.APIVersion{
{
APIGroup: "group",
Name: version,
},
},
}
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) {
for _, version := range versionsToTest {
testInstallThirdPartyAPIListVersion(t, version)
}
}
func testInstallThirdPartyAPIListVersion(t *testing.T, version string) {
fakeClient, server := initThirdParty(t, version)
defer server.Close()
fakeClient.ExpectNotFoundGet(etcdtest.PathPrefix() + "/ThirdPartyResourceData/company.com/foos/default")
resp, err := http.Get(server.URL + "/thirdparty/company.com/" + version + "/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)
}
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) {
for _, version := range versionsToTest {
testInstallThirdPartyAPIGetVersion(t, version)
}
}
func testInstallThirdPartyAPIGetVersion(t *testing.T, version string) {
fakeClient, server := initThirdParty(t, version)
defer server.Close()
expectedObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: api.TypeMeta{
Kind: "Foo",
APIVersion: version,
},
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/" + version + "/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) {
for _, version := range versionsToTest {
testInstallThirdPartyAPIPostForVersion(t, version)
}
}
func testInstallThirdPartyAPIPostForVersion(t *testing.T, version string) {
fakeClient, server := initThirdParty(t, version)
defer server.Close()
inputObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: api.TypeMeta{
Kind: "Foo",
APIVersion: version,
},
SomeField: "test field",
OtherField: 10,
}
data, err := json.Marshal(inputObj)
if err != nil {
t.Errorf("unexpected error: %v")
return
}
resp, err := http.Post(server.URL+"/thirdparty/company.com/"+version+"/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)
t.FailNow()
}
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) {
for _, version := range versionsToTest {
testInstallThirdPartyAPIDeleteVersion(t, version)
}
}
func testInstallThirdPartyAPIDeleteVersion(t *testing.T, version string) {
fakeClient, server := initThirdParty(t, version)
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/" + version + "/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/" + version + "/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/" + version + "/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)
}

View File

@ -0,0 +1,274 @@
/*
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 (
"bytes"
"encoding/json"
"fmt"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/expapi/latest"
"k8s.io/kubernetes/pkg/runtime"
)
type thirdPartyResourceDataMapper struct {
mapper meta.RESTMapper
kind string
version string
}
func (t *thirdPartyResourceDataMapper) GroupForResource(resource string) (string, error) {
return t.mapper.GroupForResource(resource)
}
func (t *thirdPartyResourceDataMapper) RESTMapping(kind string, versions ...string) (*meta.RESTMapping, error) {
if len(versions) != 1 {
return nil, fmt.Errorf("unexpected set of versions: %v", versions)
}
if versions[0] != t.version {
return nil, fmt.Errorf("unknown version %s expected %s", versions[0], t.version)
}
if kind != "ThirdPartyResourceData" {
return nil, fmt.Errorf("unknown kind %s expected %s", kind, t.kind)
}
mapping, err := t.mapper.RESTMapping("ThirdPartyResourceData", latest.Version)
if err != nil {
return nil, err
}
mapping.Codec = NewCodec(mapping.Codec, t.kind)
return mapping, nil
}
func (t *thirdPartyResourceDataMapper) AliasesForResource(resource string) ([]string, bool) {
return t.mapper.AliasesForResource(resource)
}
func (t *thirdPartyResourceDataMapper) ResourceSingularizer(resource string) (singular string, err error) {
return t.mapper.ResourceSingularizer(resource)
}
func (t *thirdPartyResourceDataMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
return t.mapper.VersionAndKindForResource(resource)
}
func NewMapper(mapper meta.RESTMapper, kind, version string) meta.RESTMapper {
return &thirdPartyResourceDataMapper{
mapper: mapper,
kind: kind,
version: version,
}
}
type thirdPartyResourceDataCodec struct {
delegate runtime.Codec
kind string
}
func NewCodec(codec runtime.Codec, kind string) runtime.Codec {
return &thirdPartyResourceDataCodec{codec, kind}
}
func (t *thirdPartyResourceDataCodec) populate(objIn *expapi.ThirdPartyResourceData, data []byte) error {
var obj interface{}
if err := json.Unmarshal(data, &obj); err != nil {
fmt.Printf("Invalid JSON:\n%s\n", string(data))
return err
}
mapObj, ok := obj.(map[string]interface{})
if !ok {
return fmt.Errorf("unexpected object: %#v", obj)
}
return t.populateFromObject(objIn, mapObj, data)
}
func (t *thirdPartyResourceDataCodec) populateFromObject(objIn *expapi.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error {
kind, ok := mapObj["kind"].(string)
if !ok {
return fmt.Errorf("unexpected object for kind: %#v", mapObj["kind"])
}
if kind != t.kind {
return fmt.Errorf("unexpected kind: %s, expected: %s", kind, t.kind)
}
metadata, ok := mapObj["metadata"].(map[string]interface{})
if !ok {
return fmt.Errorf("unexpected object for metadata: %#v", mapObj["metadata"])
}
if resourceVersion, ok := metadata["resourceVersion"]; ok {
resourceVersionStr, ok := resourceVersion.(string)
if !ok {
return fmt.Errorf("unexpected object for resourceVersion: %v", resourceVersion)
}
objIn.ResourceVersion = resourceVersionStr
}
name, ok := metadata["name"].(string)
if !ok {
return fmt.Errorf("unexpected object for name: %#v", metadata)
}
if labels, ok := metadata["labels"]; ok {
labelMap, ok := labels.(map[string]interface{})
if !ok {
return fmt.Errorf("unexpected object for labels: %v", labelMap)
}
for key, value := range labelMap {
valueStr, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected label: %v", value)
}
if objIn.Labels == nil {
objIn.Labels = map[string]string{}
}
objIn.Labels[key] = valueStr
}
}
objIn.Name = name
objIn.Data = data
return nil
}
func (t *thirdPartyResourceDataCodec) Decode(data []byte) (runtime.Object, error) {
result := &expapi.ThirdPartyResourceData{}
if err := t.populate(result, data); err != nil {
return nil, err
}
return result, nil
}
func (t *thirdPartyResourceDataCodec) DecodeToVersion(data []byte, version string) (runtime.Object, error) {
// TODO: this is hacky, there must be a better way...
obj, err := t.Decode(data)
if err != nil {
return nil, err
}
objData, err := t.delegate.Encode(obj)
if err != nil {
return nil, err
}
return t.delegate.DecodeToVersion(objData, version)
}
func (t *thirdPartyResourceDataCodec) DecodeInto(data []byte, obj runtime.Object) error {
thirdParty, ok := obj.(*expapi.ThirdPartyResourceData)
if !ok {
return fmt.Errorf("unexpected object: %#v", obj)
}
return t.populate(thirdParty, data)
}
func (t *thirdPartyResourceDataCodec) DecodeIntoWithSpecifiedVersionKind(data []byte, obj runtime.Object, version, kind string) error {
thirdParty, ok := obj.(*expapi.ThirdPartyResourceData)
if !ok {
return fmt.Errorf("unexpected object: %#v", obj)
}
if kind != "ThirdPartyResourceData" {
return fmt.Errorf("unexpeceted kind: %s", kind)
}
var dataObj interface{}
if err := json.Unmarshal(data, &dataObj); err != nil {
return err
}
mapObj, ok := dataObj.(map[string]interface{})
if !ok {
return fmt.Errorf("unexpcted object: %#v", dataObj)
}
if kindObj, found := mapObj["kind"]; !found {
mapObj["kind"] = kind
} else {
kindStr, ok := kindObj.(string)
if !ok {
return fmt.Errorf("unexpected object for 'kind': %v", kindObj)
}
if kindStr != t.kind {
return fmt.Errorf("kind doesn't match, expecting: %s, got %s", kind, kindStr)
}
}
if versionObj, found := mapObj["apiVersion"]; !found {
mapObj["apiVersion"] = version
} else {
versionStr, ok := versionObj.(string)
if !ok {
return fmt.Errorf("unexpected object for 'apiVersion': %v", versionObj)
}
if versionStr != version {
return fmt.Errorf("version doesn't match, expecting: %s, got %s", version, versionStr)
}
}
if err := t.populate(thirdParty, data); err != nil {
return err
}
return nil
}
const template = `{
"kind": "%s",
"items": [ %s ]
}`
func (t *thirdPartyResourceDataCodec) Encode(obj runtime.Object) (data []byte, err error) {
switch obj := obj.(type) {
case *expapi.ThirdPartyResourceData:
return obj.Data, nil
case *expapi.ThirdPartyResourceDataList:
// TODO: There must be a better way to do this...
buff := &bytes.Buffer{}
dataStrings := make([]string, len(obj.Items))
for ix := range obj.Items {
dataStrings[ix] = string(obj.Items[ix].Data)
}
fmt.Fprintf(buff, template, t.kind+"List", strings.Join(dataStrings, ","))
return buff.Bytes(), nil
case *api.Status:
return t.delegate.Encode(obj)
default:
return nil, fmt.Errorf("unexpected object to encode: %#v", obj)
}
}
func NewObjectCreator(version string, delegate runtime.ObjectCreater) runtime.ObjectCreater {
return &thirdPartyResourceDataCreator{version, delegate}
}
type thirdPartyResourceDataCreator struct {
version string
delegate runtime.ObjectCreater
}
func (t *thirdPartyResourceDataCreator) New(version, kind string) (out runtime.Object, err error) {
if t.version != version {
return nil, fmt.Errorf("unknown version %s for kind %s", version, kind)
}
switch kind {
case "ThirdPartyResourceData":
return &expapi.ThirdPartyResourceData{}, nil
case "ThirdPartyResourceDataList":
return &expapi.ThirdPartyResourceDataList{}, nil
default:
return t.delegate.New(latest.Version, kind)
}
}

View File

@ -0,0 +1,123 @@
/*
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 (
"encoding/json"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/expapi"
)
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 TestCodec(t *testing.T) {
tests := []struct {
obj *Foo
expectErr bool
name string
}{
{
obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar"}},
expectErr: true,
name: "missing kind",
},
{
obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar"}, TypeMeta: api.TypeMeta{Kind: "Foo"}},
name: "basic",
},
{
obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "baz"}, TypeMeta: api.TypeMeta{Kind: "Foo"}},
name: "resource version",
},
{
obj: &Foo{
ObjectMeta: api.ObjectMeta{
Name: "bar",
ResourceVersion: "baz",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
TypeMeta: api.TypeMeta{Kind: "Foo"},
},
name: "labels",
},
}
for _, test := range tests {
codec := thirdPartyResourceDataCodec{kind: "Foo"}
data, err := json.Marshal(test.obj)
if err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
continue
}
obj, err := codec.Decode(data)
if err != nil && !test.expectErr {
t.Errorf("[%s] unexpected error: %v", test.name, err)
continue
}
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
}
continue
}
rsrcObj, ok := obj.(*expapi.ThirdPartyResourceData)
if !ok {
t.Errorf("[%s] unexpected object: %v", test.name, obj)
continue
}
if !reflect.DeepEqual(rsrcObj.ObjectMeta, test.obj.ObjectMeta) {
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, rsrcObj.ObjectMeta, test.obj.ObjectMeta)
}
var output Foo
if err := json.Unmarshal(rsrcObj.Data, &output); err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
continue
}
if !reflect.DeepEqual(&output, test.obj) {
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output)
}
data, err = codec.Encode(rsrcObj)
if err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
}
var output2 Foo
if err := json.Unmarshal(data, &output2); err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
continue
}
if !reflect.DeepEqual(&output2, test.obj) {
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output2)
}
}
}

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,65 @@
/*
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 (
"strings"
"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, group, kind string) *REST {
prefix := "/ThirdPartyResourceData/" + group + "/" + strings.ToLower(kind) + "s"
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,174 @@
/*
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.ThirdPartyResourceData{}
}
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, "foo", "bar")
return storage, fakeEtcdClient, etcdStorage
}
func validNewThirdPartyResourceData(name string) *expapi.ThirdPartyResourceData {
return &expapi.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
},
Data: []byte("foobarbaz"),
}
}
func TestCreate(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
rsrc := validNewThirdPartyResourceData("foo")
rsrc.ObjectMeta = api.ObjectMeta{}
test.TestCreate(
// valid
rsrc,
// invalid
&expapi.ThirdPartyResourceData{},
)
}
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 := validNewThirdPartyResourceData("foo")
existing := validNewThirdPartyResourceData("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.ThirdPartyResourceData)
older.ResourceVersion = "1"
test.TestUpdate(
rsrc,
existing,
older,
)
}
func TestGet(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
rsrc := validNewThirdPartyResourceData("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.ThirdPartyResourceDataList).Items) != 0 {
t.Errorf("Unexpected non-zero autoscaler list: %#v", rsrcList)
}
if rsrcList.(*expapi.ThirdPartyResourceDataList).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.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}),
},
{
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResourceData{
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.ThirdPartyResourceDataList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(rsrcList.Items) != 2 {
t.Errorf("Unexpected ThirdPartyResourceData list: %#v", rsrcList)
}
if rsrcList.Items[0].Name != "foo" {
t.Errorf("Unexpected ThirdPartyResourceData: %#v", rsrcList.Items[0])
}
if rsrcList.Items[1].Name != "bar" {
t.Errorf("Unexpected ThirdPartyResourceData: %#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.ValidateThirdPartyResourceData(obj.(*expapi.ThirdPartyResourceData))
}
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.ValidateThirdPartyResourceDataUpdate(old.(*expapi.ThirdPartyResourceData), obj.(*expapi.ThirdPartyResourceData))
}
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.ThirdPartyResourceData)
if !ok {
return false, fmt.Errorf("not a ThirdPartyResourceData")
}
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.ThirdPartyResourceData) labels.Set {
return labels.Set{}
}

View File

@ -0,0 +1,49 @@
/*
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"
"strings"
"k8s.io/kubernetes/pkg/expapi"
)
func convertToCamelCase(input string) string {
result := ""
toUpper := true
for ix := range input {
char := input[ix]
if toUpper {
result = result + string([]byte{(char - 32)})
toUpper = false
} else if char == '-' {
toUpper = true
} else {
result = result + string([]byte{char})
}
}
return result
}
func ExtractApiGroupAndKind(rsrc *expapi.ThirdPartyResource) (kind string, group string, err error) {
parts := strings.Split(rsrc.Name, ".")
if len(parts) < 3 {
return "", "", fmt.Errorf("unexpectedly short resource name: %s, expected at least <kind>.<domain>.<tld>", rsrc.Name)
}
return convertToCamelCase(parts[0]), strings.Join(parts[1:], "."), nil
}

View File

@ -0,0 +1,66 @@
/*
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 (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/expapi"
)
func TestExtractAPIGroupAndKind(t *testing.T) {
tests := []struct {
input string
expectedKind string
expectedGroup string
expectErr bool
}{
{
input: "foo.company.com",
expectedKind: "Foo",
expectedGroup: "company.com",
},
{
input: "cron-tab.company.com",
expectedKind: "CronTab",
expectedGroup: "company.com",
},
{
input: "foo",
expectErr: true,
},
}
for _, test := range tests {
kind, group, err := ExtractApiGroupAndKind(&expapi.ThirdPartyResource{ObjectMeta: api.ObjectMeta{Name: test.input}})
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
continue
}
if err == nil && test.expectErr {
t.Errorf("unexpected non-error")
continue
}
if kind != test.expectedKind {
t.Errorf("expected: %s, saw: %s", test.expectedKind, kind)
}
if group != test.expectedGroup {
t.Errorf("expected: %s, saw: %s", test.expectedGroup, group)
}
}
}