diff --git a/cmd/kubecfg/kubecfg.go b/cmd/kubecfg/kubecfg.go index 9708946dec1..87c96b13bf6 100644 --- a/cmd/kubecfg/kubecfg.go +++ b/cmd/kubecfg/kubecfg.go @@ -31,6 +31,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -372,11 +373,11 @@ func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool { if err != nil { glog.Fatalf("error obtaining resource version for update: %v", err) } - jsonBase, err := runtime.FindTypeMeta(obj) + meta, err := meta.Accessor(obj) if err != nil { glog.Fatalf("error finding json base for update: %v", err) } - version = jsonBase.ResourceVersion() + version = meta.ResourceVersion() verb = "PUT" setBody = true if !validStorage || !hasSuffix { @@ -408,7 +409,7 @@ func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool { if err != nil { glog.Fatalf("error setting resource version: %v", err) } - jsonBase, err := runtime.FindTypeMeta(obj) + jsonBase, err := meta.Accessor(obj) if err != nil { glog.Fatalf("error setting resource version: %v", err) } diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index 5dec503baf8..4c2bdc5d467 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -20,6 +20,7 @@ import ( "fmt" "strings" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -47,13 +48,13 @@ var Codec = v1beta1.Codec // ResourceVersioner describes a default versioner that can handle all types // of versioning. // TODO: when versioning changes, make this part of each API definition. -var ResourceVersioner = runtime.NewTypeMetaResourceVersioner() +var ResourceVersioner = meta.NewResourceVersioner() // SelfLinker can set or get the SelfLink field of all API types. // TODO: when versioning changes, make this part of each API definition. // TODO(lavalamp): Combine SelfLinker & ResourceVersioner interfaces, force all uses // to go through the InterfacesFor method below. -var SelfLinker = runtime.NewTypeMetaSelfLinker() +var SelfLinker = meta.NewSelfLinker() // VersionInterfaces contains the interfaces one should use for dealing with types of a particular version. type VersionInterfaces struct { diff --git a/pkg/api/meta/doc.go b/pkg/api/meta/doc.go new file mode 100644 index 00000000000..a388f1d7ec5 --- /dev/null +++ b/pkg/api/meta/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package meta provides functions for retrieving API metadata from objects +// belonging to the Kubernetes API +package meta diff --git a/pkg/api/meta/meta.go b/pkg/api/meta/meta.go new file mode 100644 index 00000000000..6509fbabfd7 --- /dev/null +++ b/pkg/api/meta/meta.go @@ -0,0 +1,281 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "fmt" + "reflect" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +// Interface lets you work with object metadata from any of the versioned or +// internal API objects. +type Interface interface { + Name() string + SetName(name string) + UID() string + SetUID(uid string) + APIVersion() string + SetAPIVersion(version string) + Kind() string + SetKind(kind string) + ResourceVersion() string + SetResourceVersion(version string) + SelfLink() string + SetSelfLink(selfLink string) +} + +// Accessor takes an arbitary object pointer and returns meta.Interface. +// obj must be a pointer to an API type. An error is returned if the minimum +// required fields are missing. Fields that are not required return the default +// value and are a no-op if set. +func Accessor(obj interface{}) (Interface, error) { + v, err := conversion.EnforcePtr(obj) + if err != nil { + return nil, err + } + t := v.Type() + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), t, v.Interface()) + } + + typeMeta := v.FieldByName("TypeMeta") + if !typeMeta.IsValid() { + return nil, fmt.Errorf("struct %v lacks embedded TypeMeta type", t) + } + + a := &genericAccessor{} + if err := extractFromTypeMeta(typeMeta, a); err != nil { + return nil, fmt.Errorf("unable to find type fields on %#v", typeMeta) + } + + objectMeta := v.FieldByName("ObjectMeta") + if objectMeta.IsValid() { + // look for the ObjectMeta fields + if err := extractFromObjectMeta(objectMeta, a); err != nil { + return nil, fmt.Errorf("unable to find object fields on %#v", objectMeta) + } + } else { + listMeta := v.FieldByName("ListMeta") + if listMeta.IsValid() { + // look for the ListMeta fields + if err := extractFromListMeta(listMeta, a); err != nil { + return nil, fmt.Errorf("unable to find list fields on %#v", listMeta) + } + } else { + // look for the older TypeMeta with all metadata + if err := extractFromObjectMeta(typeMeta, a); err != nil { + return nil, fmt.Errorf("unable to find object fields on %#v", typeMeta) + } + } + } + + return a, nil +} + +// NewResourceVersioner returns a ResourceVersioner that can set or +// retrieve ResourceVersion on objects derived from TypeMeta. +func NewResourceVersioner() runtime.ResourceVersioner { + return resourceAccessor{} +} + +// resourceAccessor implements ResourceVersioner and SelfLinker. +type resourceAccessor struct{} + +func (v resourceAccessor) ResourceVersion(obj runtime.Object) (string, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.ResourceVersion(), nil +} + +func (v resourceAccessor) SetResourceVersion(obj runtime.Object, version string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetResourceVersion(version) + return nil +} + +func (v resourceAccessor) Name(obj runtime.Object) (string, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.Name(), nil +} + +func (v resourceAccessor) SelfLink(obj runtime.Object) (string, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.SelfLink(), nil +} + +func (v resourceAccessor) SetSelfLink(obj runtime.Object, selfLink string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetSelfLink(selfLink) + return nil +} + +// NewSelfLinker returns a SelfLinker that works on all TypeMeta SelfLink fields. +func NewSelfLinker() runtime.SelfLinker { + return resourceAccessor{} +} + +// genericAccessor contains pointers to strings that can modify an arbitrary +// struct and implements the Accessor interface. +type genericAccessor struct { + name *string + uid *string + apiVersion *string + kind *string + resourceVersion *string + selfLink *string +} + +func (a genericAccessor) Name() string { + if a.name == nil { + return "" + } + return *a.name +} + +func (a genericAccessor) SetName(name string) { + if a.name == nil { + return + } + *a.name = name +} + +func (a genericAccessor) UID() string { + if a.uid == nil { + return "" + } + return *a.uid +} + +func (a genericAccessor) SetUID(uid string) { + if a.uid == nil { + return + } + *a.uid = uid +} + +func (a genericAccessor) APIVersion() string { + return *a.apiVersion +} + +func (a genericAccessor) SetAPIVersion(version string) { + *a.apiVersion = version +} + +func (a genericAccessor) Kind() string { + return *a.kind +} + +func (a genericAccessor) SetKind(kind string) { + *a.kind = kind +} + +func (a genericAccessor) ResourceVersion() string { + return *a.resourceVersion +} + +func (a genericAccessor) SetResourceVersion(version string) { + *a.resourceVersion = version +} + +func (a genericAccessor) SelfLink() string { + return *a.selfLink +} + +func (a genericAccessor) SetSelfLink(selfLink string) { + *a.selfLink = selfLink +} + +// fieldPtr puts the address of fieldName, which must be a member of v, +// into dest, which must be an address of a variable to which this field's +// address can be assigned. +func fieldPtr(v reflect.Value, fieldName string, dest interface{}) error { + field := v.FieldByName(fieldName) + if !field.IsValid() { + return fmt.Errorf("Couldn't find %v field in %#v", fieldName, v.Interface()) + } + v = reflect.ValueOf(dest) + if v.Kind() != reflect.Ptr { + return fmt.Errorf("dest should be ptr") + } + v = v.Elem() + field = field.Addr() + if field.Type().AssignableTo(v.Type()) { + v.Set(field) + return nil + } + if field.Type().ConvertibleTo(v.Type()) { + v.Set(field.Convert(v.Type())) + return nil + } + return fmt.Errorf("Couldn't assign/convert %v to %v", field.Type(), v.Type()) +} + +// extractFromTypeMeta extracts pointers to version and kind fields from an object +func extractFromTypeMeta(v reflect.Value, a *genericAccessor) error { + if err := fieldPtr(v, "APIVersion", &a.apiVersion); err != nil { + return err + } + if err := fieldPtr(v, "Kind", &a.kind); err != nil { + return err + } + return nil +} + +// extractFromObjectMeta extracts pointers to metadata fields from an object +func extractFromObjectMeta(v reflect.Value, a *genericAccessor) error { + if err := fieldPtr(v, "Name", &a.name); err != nil { + return err + } + if err := fieldPtr(v, "UID", &a.uid); err != nil { + return err + } + if err := fieldPtr(v, "ResourceVersion", &a.resourceVersion); err != nil { + return err + } + if err := fieldPtr(v, "SelfLink", &a.selfLink); err != nil { + return err + } + return nil +} + +// extractFromObjectMeta extracts pointers to metadata fields from a list object +func extractFromListMeta(v reflect.Value, a *genericAccessor) error { + if err := fieldPtr(v, "ResourceVersion", &a.resourceVersion); err != nil { + return err + } + if err := fieldPtr(v, "SelfLink", &a.selfLink); err != nil { + return err + } + return nil +} diff --git a/pkg/api/meta/meta_test.go b/pkg/api/meta/meta_test.go new file mode 100644 index 00000000000..1d7653e16fe --- /dev/null +++ b/pkg/api/meta/meta_test.go @@ -0,0 +1,355 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +func TestGenericTypeMeta(t *testing.T) { + type TypeMeta struct { + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + UID string `json:"uid,omitempty" yaml:"uid,omitempty"` + CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` + SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + } + type Object struct { + TypeMeta `json:",inline" yaml:",inline"` + } + j := Object{ + TypeMeta{ + Name: "foo", + UID: "uid", + APIVersion: "a", + Kind: "b", + ResourceVersion: "1", + SelfLink: "some/place/only/we/know", + }, + } + accessor, err := Accessor(&j) + if err != nil { + t.Fatalf("new err: %v", err) + } + if e, a := "foo", accessor.Name(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "uid", accessor.UID(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "a", accessor.APIVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "b", accessor.Kind(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "1", accessor.ResourceVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "some/place/only/we/know", accessor.SelfLink(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + accessor.SetName("bar") + accessor.SetUID("other") + accessor.SetAPIVersion("c") + accessor.SetKind("d") + accessor.SetResourceVersion("2") + accessor.SetSelfLink("google.com") + + // Prove that accessor changes the original object. + if e, a := "bar", j.Name; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "other", j.UID; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "c", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "d", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "2", j.ResourceVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "google.com", j.SelfLink; e != a { + t.Errorf("expected %v, got %v", e, a) + } +} + +func TestGenericObjectMeta(t *testing.T) { + type TypeMeta struct { + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + } + type ObjectMeta struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + UID string `json:"uid,omitempty" yaml:"uid,omitempty"` + CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` + SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` + } + type Object struct { + TypeMeta `json:",inline" yaml:",inline"` + ObjectMeta `json:"metadata" yaml:"metadata"` + } + j := Object{ + TypeMeta{ + APIVersion: "a", + Kind: "b", + }, + ObjectMeta{ + Name: "foo", + UID: "uid", + ResourceVersion: "1", + SelfLink: "some/place/only/we/know", + }, + } + accessor, err := Accessor(&j) + if err != nil { + t.Fatalf("new err: %v", err) + } + if e, a := "foo", accessor.Name(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "uid", accessor.UID(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "a", accessor.APIVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "b", accessor.Kind(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "1", accessor.ResourceVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "some/place/only/we/know", accessor.SelfLink(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + accessor.SetName("bar") + accessor.SetUID("other") + accessor.SetAPIVersion("c") + accessor.SetKind("d") + accessor.SetResourceVersion("2") + accessor.SetSelfLink("google.com") + + // Prove that accessor changes the original object. + if e, a := "bar", j.Name; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "other", j.UID; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "c", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "d", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "2", j.ResourceVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "google.com", j.SelfLink; e != a { + t.Errorf("expected %v, got %v", e, a) + } +} + +func TestGenericListMeta(t *testing.T) { + type TypeMeta struct { + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + } + type ListMeta struct { + SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` + } + type Object struct { + TypeMeta `json:",inline" yaml:",inline"` + ListMeta `json:"metadata" yaml:"metadata"` + } + j := Object{ + TypeMeta{ + APIVersion: "a", + Kind: "b", + }, + ListMeta{ + ResourceVersion: "1", + SelfLink: "some/place/only/we/know", + }, + } + accessor, err := Accessor(&j) + if err != nil { + t.Fatalf("new err: %v", err) + } + if e, a := "", accessor.Name(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "", accessor.UID(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "a", accessor.APIVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "b", accessor.Kind(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "1", accessor.ResourceVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "some/place/only/we/know", accessor.SelfLink(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + accessor.SetName("bar") + accessor.SetUID("other") + accessor.SetAPIVersion("c") + accessor.SetKind("d") + accessor.SetResourceVersion("2") + accessor.SetSelfLink("google.com") + + // Prove that accessor changes the original object. + if e, a := "c", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "d", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "2", j.ResourceVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "google.com", j.SelfLink; e != a { + t.Errorf("expected %v, got %v", e, a) + } +} + +type MyAPIObject struct { + runtime.TypeMeta `yaml:",inline" json:",inline"` +} + +func (*MyAPIObject) IsAnAPIObject() {} + +type MyIncorrectlyMarkedAsAPIObject struct { +} + +func (*MyIncorrectlyMarkedAsAPIObject) IsAnAPIObject() {} + +func TestResourceVersionerOfAPI(t *testing.T) { + type T struct { + runtime.Object + Expected string + } + testCases := map[string]T{ + "empty api object": {&MyAPIObject{}, ""}, + "api object with version": {&MyAPIObject{TypeMeta: runtime.TypeMeta{ResourceVersion: "1"}}, "1"}, + "pointer to api object with version": {&MyAPIObject{TypeMeta: runtime.TypeMeta{ResourceVersion: "1"}}, "1"}, + } + versioning := NewResourceVersioner() + for key, testCase := range testCases { + actual, err := versioning.ResourceVersion(testCase.Object) + if err != nil { + t.Errorf("%s: unexpected error %#v", key, err) + } + if actual != testCase.Expected { + t.Errorf("%s: expected %v, got %v", key, testCase.Expected, actual) + } + } + + failingCases := map[string]struct { + runtime.Object + Expected string + }{ + "not a valid object to try": {&MyIncorrectlyMarkedAsAPIObject{}, "1"}, + } + for key, testCase := range failingCases { + _, err := versioning.ResourceVersion(testCase.Object) + if err == nil { + t.Errorf("%s: expected error, got nil", key) + } + } + + setCases := map[string]struct { + runtime.Object + Expected string + }{ + "pointer to api object with version": {&MyAPIObject{TypeMeta: runtime.TypeMeta{ResourceVersion: "1"}}, "1"}, + } + for key, testCase := range setCases { + if err := versioning.SetResourceVersion(testCase.Object, "5"); err != nil { + t.Errorf("%s: unexpected error %#v", key, err) + } + actual, err := versioning.ResourceVersion(testCase.Object) + if err != nil { + t.Errorf("%s: unexpected error %#v", key, err) + } + if actual != "5" { + t.Errorf("%s: expected %v, got %v", key, "5", actual) + } + } +} + +func TestTypeMetaSelfLinker(t *testing.T) { + table := map[string]struct { + obj runtime.Object + expect string + try string + succeed bool + }{ + "normal": { + obj: &MyAPIObject{TypeMeta: runtime.TypeMeta{SelfLink: "foobar"}}, + expect: "foobar", + try: "newbar", + succeed: true, + }, + "fail": { + obj: &MyIncorrectlyMarkedAsAPIObject{}, + succeed: false, + }, + } + + linker := NewSelfLinker() + for name, item := range table { + got, err := linker.SelfLink(item.obj) + if e, a := item.succeed, err == nil; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + if e, a := item.expect, got; item.succeed && e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + + err = linker.SetSelfLink(item.obj, item.try) + if e, a := item.succeed, err == nil; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + if item.succeed { + got, err := linker.SelfLink(item.obj) + if err != nil { + t.Errorf("%v: expected no err, got %v", name, err) + } + if e, a := item.try, got; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + } + } +} diff --git a/pkg/api/ref.go b/pkg/api/ref.go index fc6fd51fc52..c21533fe7fa 100644 --- a/pkg/api/ref.go +++ b/pkg/api/ref.go @@ -21,6 +21,7 @@ import ( "fmt" "regexp" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -35,7 +36,7 @@ func GetReference(obj runtime.Object) (*ObjectReference, error) { if obj == nil { return nil, ErrNilObject } - jsonBase, err := runtime.FindTypeMeta(obj) + meta, err := meta.Accessor(obj) if err != nil { return nil, err } @@ -43,16 +44,16 @@ func GetReference(obj runtime.Object) (*ObjectReference, error) { if err != nil { return nil, err } - version := versionFromSelfLink.FindStringSubmatch(jsonBase.SelfLink()) + version := versionFromSelfLink.FindStringSubmatch(meta.SelfLink()) if len(version) < 2 { - return nil, fmt.Errorf("unexpected self link format: %v", jsonBase.SelfLink()) + return nil, fmt.Errorf("unexpected self link format: %v", meta.SelfLink()) } return &ObjectReference{ Kind: kind, APIVersion: version[1], // TODO: correct Name and UID when TypeMeta makes a distinction - Name: jsonBase.ID(), - UID: jsonBase.ID(), - ResourceVersion: jsonBase.ResourceVersion(), + Name: meta.Name(), + UID: meta.UID(), + ResourceVersion: meta.ResourceVersion(), }, nil } diff --git a/pkg/api/ref_test.go b/pkg/api/ref_test.go index 14be5bb1600..fefb668c8a5 100644 --- a/pkg/api/ref_test.go +++ b/pkg/api/ref_test.go @@ -37,6 +37,7 @@ func TestGetReference(t *testing.T) { obj: &Pod{ TypeMeta: TypeMeta{ Name: "foo", + UID: "bar", ResourceVersion: "42", SelfLink: "/api/v1beta1/pods/foo", }, @@ -45,7 +46,7 @@ func TestGetReference(t *testing.T) { Kind: "Pod", APIVersion: "v1beta1", Name: "foo", - UID: "foo", + UID: "bar", ResourceVersion: "42", }, }, @@ -53,6 +54,7 @@ func TestGetReference(t *testing.T) { obj: &ServiceList{ TypeMeta: TypeMeta{ Name: "foo", + UID: "bar", ResourceVersion: "42", SelfLink: "/api/v1beta2/services", }, @@ -61,7 +63,7 @@ func TestGetReference(t *testing.T) { Kind: "ServiceList", APIVersion: "v1beta2", Name: "foo", - UID: "foo", + UID: "bar", ResourceVersion: "42", }, }, diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index e5c9c2371e7..731e680a0f5 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -46,17 +47,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" - j.Name = c.RandString() - // TODO: Fix JSON/YAML packages and/or write custom encoding - // for uint64's. Somehow the LS *byte* of this is lost, but - // only when all 8 bytes are set. - j.ResourceVersion = strconv.FormatUint(c.RandUint64()>>8, 10) - j.SelfLink = c.RandString() - - var sec, nsec int64 - c.Fuzz(&sec) - c.Fuzz(&nsec) - j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their @@ -111,7 +101,7 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { name := reflect.TypeOf(source).Elem().Name() apiObjectFuzzer.Fuzz(source) - j, err := runtime.FindTypeMeta(source) + j, err := meta.Accessor(source) if err != nil { t.Fatalf("Unexpected error %v for %#v", err, source) } @@ -170,7 +160,7 @@ func TestTypes(t *testing.T) { t.Errorf("Couldn't make a %v? %v", kind, err) continue } - if _, err := runtime.FindTypeMeta(item); err != nil { + if _, err := meta.Accessor(item); err != nil { t.Logf("%s is not a TypeMeta and cannot be round tripped: %v", kind, err) continue } diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 4a485e9c781..fddd2a1c8fd 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -564,11 +564,11 @@ func TestParseTimeout(t *testing.T) { type setTestSelfLinker struct { t *testing.T expectedSet string - id string + name string called bool } -func (s *setTestSelfLinker) ID(runtime.Object) (string, error) { return s.id, nil } +func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, nil } func (*setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", nil } func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error { if e, a := s.expectedSet, selfLink; e != a { @@ -587,7 +587,7 @@ func TestSyncCreate(t *testing.T) { } selfLinker := &setTestSelfLinker{ t: t, - id: "bar", + name: "bar", expectedSet: "/prefix/version/foo/bar", } handler := Handle(map[string]RESTStorage{ @@ -684,7 +684,7 @@ func TestAsyncCreateError(t *testing.T) { } selfLinker := &setTestSelfLinker{ t: t, - id: "bar", + name: "bar", expectedSet: "/prefix/version/foo/bar", } handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version", selfLinker) diff --git a/pkg/apiserver/resthandler.go b/pkg/apiserver/resthandler.go index 0f368305ec1..edbcaa52371 100644 --- a/pkg/apiserver/resthandler.go +++ b/pkg/apiserver/resthandler.go @@ -64,14 +64,14 @@ func (h *RESTHandler) setSelfLink(obj runtime.Object, req *http.Request) error { return h.selfLinker.SetSelfLink(obj, newURL.String()) } -// Like setSelfLink, but appends the object's id. -func (h *RESTHandler) setSelfLinkAddID(obj runtime.Object, req *http.Request) error { - id, err := h.selfLinker.ID(obj) +// Like setSelfLink, but appends the object's name. +func (h *RESTHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error { + name, err := h.selfLinker.Name(obj) if err != nil { return err } newURL := *req.URL - newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, id) + newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name) newURL.RawQuery = "" newURL.Fragment = "" return h.selfLinker.SetSelfLink(obj, newURL.String()) @@ -171,7 +171,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt errorJSON(err, h.codec, w) return } - op := h.createOperation(out, sync, timeout, curry(h.setSelfLinkAddID, req)) + op := h.createOperation(out, sync, timeout, curry(h.setSelfLinkAddName, req)) h.finishReq(op, req, w) case "DELETE": diff --git a/pkg/client/cache/reflector.go b/pkg/client/cache/reflector.go index 20298657260..b360699a6ce 100644 --- a/pkg/client/cache/reflector.go +++ b/pkg/client/cache/reflector.go @@ -23,6 +23,7 @@ import ( "time" apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -78,12 +79,12 @@ func (r *Reflector) listAndWatch() { glog.Errorf("Failed to list %v: %v", r.expectedType, err) return } - jsonBase, err := runtime.FindTypeMeta(list) + meta, err := meta.Accessor(list) if err != nil { glog.Errorf("Unable to understand list result %#v", list) return } - resourceVersion = jsonBase.ResourceVersion() + resourceVersion = meta.ResourceVersion() items, err := runtime.ExtractList(list) if err != nil { glog.Errorf("Unable to understand list result %#v (%v)", list, err) @@ -112,11 +113,11 @@ func (r *Reflector) listAndWatch() { func (r *Reflector) syncWith(items []runtime.Object) error { found := map[string]interface{}{} for _, item := range items { - jsonBase, err := runtime.FindTypeMeta(item) + meta, err := meta.Accessor(item) if err != nil { return fmt.Errorf("unexpected item in list: %v", err) } - found[jsonBase.ID()] = item + found[meta.Name()] = item } r.store.Replace(found) @@ -139,25 +140,25 @@ func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string) err glog.Errorf("expected type %v, but watch event object had type %v", e, a) continue } - jsonBase, err := runtime.FindTypeMeta(event.Object) + meta, err := meta.Accessor(event.Object) if err != nil { glog.Errorf("unable to understand watch event %#v", event) continue } switch event.Type { case watch.Added: - r.store.Add(jsonBase.ID(), event.Object) + r.store.Add(meta.Name(), event.Object) case watch.Modified: - r.store.Update(jsonBase.ID(), event.Object) + r.store.Update(meta.Name(), event.Object) case watch.Deleted: // TODO: Will any consumers need access to the "last known // state", which is passed in event.Object? If so, may need // to change this. - r.store.Delete(jsonBase.ID()) + r.store.Delete(meta.Name()) default: glog.Errorf("unable to understand watch event %#v", event) } - *resourceVersion = jsonBase.ResourceVersion() + *resourceVersion = meta.ResourceVersion() eventCount++ } diff --git a/pkg/client/record/event_test.go b/pkg/client/record/event_test.go index 9c58b23ec18..d515d28d0bf 100644 --- a/pkg/client/record/event_test.go +++ b/pkg/client/record/event_test.go @@ -57,6 +57,7 @@ func TestEventf(t *testing.T) { TypeMeta: api.TypeMeta{ SelfLink: "/api/v1beta1/pods/foo", Name: "foo", + UID: "bar", }, }, fieldPath: "desiredState.manifest.containers[2]", @@ -68,7 +69,7 @@ func TestEventf(t *testing.T) { InvolvedObject: api.ObjectReference{ Kind: "Pod", Name: "foo", - UID: "foo", + UID: "bar", APIVersion: "v1beta1", FieldPath: "desiredState.manifest.containers[2]", }, @@ -77,7 +78,7 @@ func TestEventf(t *testing.T) { Message: "some verbose message: 1", Source: "eventTest", }, - expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"", Name:"foo", UID:"foo", APIVersion:"v1beta1", ResourceVersion:"", FieldPath:"desiredState.manifest.containers[2]"}): status: 'running', reason: 'started' some verbose message: 1`, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"", Name:"foo", UID:"bar", APIVersion:"v1beta1", ResourceVersion:"", FieldPath:"desiredState.manifest.containers[2]"}): status: 'running', reason: 'started' some verbose message: 1`, }, } diff --git a/pkg/kubectl/modify.go b/pkg/kubectl/modify.go index 3c06734d9f7..eeeda63cd1f 100644 --- a/pkg/kubectl/modify.go +++ b/pkg/kubectl/modify.go @@ -20,6 +20,7 @@ import ( "fmt" "io" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -109,11 +110,11 @@ func doUpdate(c *client.RESTClient, resource string, obj runtime.Object) (string // Update the object we are trying to send to the server with the // correct resource version. - typeMeta, err := runtime.FindTypeMeta(obj) + meta, err := meta.Accessor(obj) if err != nil { return "", err } - typeMeta.SetResourceVersion(version) + meta.SetResourceVersion(version) // Convert object with updated resourceVersion to data for PUT. data, err := c.Codec.Encode(obj) @@ -149,17 +150,17 @@ func doDelete(c *client.RESTClient, resource string, obj runtime.Object) (string } func getIDFromObj(obj runtime.Object) (string, error) { - typeMeta, err := runtime.FindTypeMeta(obj) + meta, err := meta.Accessor(obj) if err != nil { return "", err } - return typeMeta.ID(), nil + return meta.Name(), nil } func getResourceVersionFromObj(obj runtime.Object) (string, error) { - typeMeta, err := runtime.FindTypeMeta(obj) + meta, err := meta.Accessor(obj) if err != nil { return "", err } - return typeMeta.ResourceVersion(), nil + return meta.ResourceVersion(), nil } diff --git a/pkg/runtime/embedded_test.go b/pkg/runtime/embedded_test.go index 6fca4c41db0..c7b7c721d91 100644 --- a/pkg/runtime/embedded_test.go +++ b/pkg/runtime/embedded_test.go @@ -29,12 +29,14 @@ var Codec = runtime.CodecFor(scheme, "v1test") type EmbeddedTest struct { runtime.TypeMeta `yaml:",inline" json:",inline"` + ID string `yaml:"id,omitempty" json:"id,omitempty"` Object runtime.EmbeddedObject `yaml:"object,omitempty" json:"object,omitempty"` EmptyObject runtime.EmbeddedObject `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"` } type EmbeddedTestExternal struct { runtime.TypeMeta `yaml:",inline" json:",inline"` + ID string `yaml:"id,omitempty" json:"id,omitempty"` Object runtime.RawExtension `yaml:"object,omitempty" json:"object,omitempty"` EmptyObject runtime.RawExtension `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"` } @@ -49,9 +51,11 @@ func TestEmbeddedObject(t *testing.T) { outer := &EmbeddedTest{ TypeMeta: runtime.TypeMeta{Name: "outer"}, + ID: "outer", Object: runtime.EmbeddedObject{ &EmbeddedTest{ TypeMeta: runtime.TypeMeta{Name: "inner"}, + ID: "inner", }, }, } diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index b688f0ae48b..5d5e725d687 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -45,8 +45,8 @@ type SelfLinker interface { SetSelfLink(obj Object, selfLink string) error SelfLink(obj Object) (string, error) - // Knowing ID is sometimes necessary to use a SelfLinker. - ID(obj Object) (string, error) + // Knowing Name is sometimes necessary to use a SelfLinker. + Name(obj Object) (string, error) } // All api types must support the Object interface. It's deliberately tiny so that this is not an onerous diff --git a/pkg/runtime/typemeta.go b/pkg/runtime/typemeta.go deleted file mode 100644 index 6dc616bd6fc..00000000000 --- a/pkg/runtime/typemeta.go +++ /dev/null @@ -1,214 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package runtime - -import ( - "fmt" - "reflect" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" -) - -// FindTypeMeta takes an arbitary api type, returns pointer to its TypeMeta field. -// obj must be a pointer to an api type. -func FindTypeMeta(obj Object) (TypeMetaInterface, error) { - v, err := conversion.EnforcePtr(obj) - if err != nil { - return nil, err - } - t := v.Type() - name := t.Name() - if v.Kind() != reflect.Struct { - return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface()) - } - typeMeta := v.FieldByName("TypeMeta") - if !typeMeta.IsValid() { - return nil, fmt.Errorf("struct %v lacks embedded JSON type", name) - } - g, err := newGenericTypeMeta(typeMeta) - if err != nil { - return nil, err - } - return g, nil -} - -// NewTypeMetaResourceVersioner returns a ResourceVersioner that can set or -// retrieve ResourceVersion on objects derived from TypeMeta. -func NewTypeMetaResourceVersioner() ResourceVersioner { - return jsonBaseModifier{} -} - -// jsonBaseModifier implements ResourceVersioner and SelfLinker. -type jsonBaseModifier struct{} - -func (v jsonBaseModifier) ResourceVersion(obj Object) (string, error) { - json, err := FindTypeMeta(obj) - if err != nil { - return "", err - } - return json.ResourceVersion(), nil -} - -func (v jsonBaseModifier) SetResourceVersion(obj Object, version string) error { - json, err := FindTypeMeta(obj) - if err != nil { - return err - } - json.SetResourceVersion(version) - return nil -} - -func (v jsonBaseModifier) ID(obj Object) (string, error) { - json, err := FindTypeMeta(obj) - if err != nil { - return "", err - } - return json.ID(), nil -} - -func (v jsonBaseModifier) SelfLink(obj Object) (string, error) { - json, err := FindTypeMeta(obj) - if err != nil { - return "", err - } - return json.SelfLink(), nil -} - -func (v jsonBaseModifier) SetSelfLink(obj Object, selfLink string) error { - json, err := FindTypeMeta(obj) - if err != nil { - return err - } - json.SetSelfLink(selfLink) - return nil -} - -// NewTypeMetaSelfLinker returns a SelfLinker that works on all TypeMeta SelfLink fields. -func NewTypeMetaSelfLinker() SelfLinker { - return jsonBaseModifier{} -} - -// TypeMetaInterface lets you work with a TypeMeta from any of the versioned or -// internal APIObjects. -type TypeMetaInterface interface { - ID() string - SetID(ID string) - APIVersion() string - SetAPIVersion(version string) - Kind() string - SetKind(kind string) - ResourceVersion() string - SetResourceVersion(version string) - SelfLink() string - SetSelfLink(selfLink string) -} - -type genericTypeMeta struct { - id *string - apiVersion *string - kind *string - resourceVersion *string - selfLink *string -} - -func (g genericTypeMeta) ID() string { - return *g.id -} - -func (g genericTypeMeta) SetID(id string) { - *g.id = id -} - -func (g genericTypeMeta) APIVersion() string { - return *g.apiVersion -} - -func (g genericTypeMeta) SetAPIVersion(version string) { - *g.apiVersion = version -} - -func (g genericTypeMeta) Kind() string { - return *g.kind -} - -func (g genericTypeMeta) SetKind(kind string) { - *g.kind = kind -} - -func (g genericTypeMeta) ResourceVersion() string { - return *g.resourceVersion -} - -func (g genericTypeMeta) SetResourceVersion(version string) { - *g.resourceVersion = version -} - -func (g genericTypeMeta) SelfLink() string { - return *g.selfLink -} - -func (g genericTypeMeta) SetSelfLink(selfLink string) { - *g.selfLink = selfLink -} - -// fieldPtr puts the address of fieldName, which must be a member of v, -// into dest, which must be an address of a variable to which this field's -// address can be assigned. -func fieldPtr(v reflect.Value, fieldName string, dest interface{}) error { - field := v.FieldByName(fieldName) - if !field.IsValid() { - return fmt.Errorf("Couldn't find %v field in %#v", fieldName, v.Interface()) - } - v = reflect.ValueOf(dest) - if v.Kind() != reflect.Ptr { - return fmt.Errorf("dest should be ptr") - } - v = v.Elem() - field = field.Addr() - if field.Type().AssignableTo(v.Type()) { - v.Set(field) - return nil - } - if field.Type().ConvertibleTo(v.Type()) { - v.Set(field.Convert(v.Type())) - return nil - } - return fmt.Errorf("Couldn't assign/convert %v to %v", field.Type(), v.Type()) -} - -// newGenericTypeMeta creates a new generic TypeMeta from v, which must be an -// addressable/setable reflect.Value having the same fields as api.TypeMeta. -// Returns an error if this isn't the case. -func newGenericTypeMeta(v reflect.Value) (genericTypeMeta, error) { - g := genericTypeMeta{} - if err := fieldPtr(v, "Name", &g.id); err != nil { - return g, err - } - if err := fieldPtr(v, "APIVersion", &g.apiVersion); err != nil { - return g, err - } - if err := fieldPtr(v, "Kind", &g.kind); err != nil { - return g, err - } - if err := fieldPtr(v, "ResourceVersion", &g.resourceVersion); err != nil { - return g, err - } - if err := fieldPtr(v, "SelfLink", &g.selfLink); err != nil { - return g, err - } - return g, nil -} diff --git a/pkg/runtime/typemeta_test.go b/pkg/runtime/typemeta_test.go deleted file mode 100644 index bd4375048d5..00000000000 --- a/pkg/runtime/typemeta_test.go +++ /dev/null @@ -1,196 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package runtime - -import ( - "reflect" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) - -func TestGenericTypeMeta(t *testing.T) { - type TypeMeta struct { - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` - SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` - ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` - APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - } - j := TypeMeta{ - Name: "foo", - APIVersion: "a", - Kind: "b", - ResourceVersion: "1", - SelfLink: "some/place/only/we/know", - } - g, err := newGenericTypeMeta(reflect.ValueOf(&j).Elem()) - if err != nil { - t.Fatalf("new err: %v", err) - } - // Prove g supports TypeMetaInterface. - jbi := TypeMetaInterface(g) - if e, a := "foo", jbi.ID(); e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "a", jbi.APIVersion(); e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "b", jbi.Kind(); e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "1", jbi.ResourceVersion(); e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "some/place/only/we/know", jbi.SelfLink(); e != a { - t.Errorf("expected %v, got %v", e, a) - } - - jbi.SetID("bar") - jbi.SetAPIVersion("c") - jbi.SetKind("d") - jbi.SetResourceVersion("2") - jbi.SetSelfLink("google.com") - - // Prove that jbi changes the original object. - if e, a := "bar", j.Name; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "c", j.APIVersion; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "d", j.Kind; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "2", j.ResourceVersion; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := "google.com", j.SelfLink; e != a { - t.Errorf("expected %v, got %v", e, a) - } -} - -type MyAPIObject struct { - TypeMeta `yaml:",inline" json:",inline"` -} - -func (*MyAPIObject) IsAnAPIObject() {} - -type MyIncorrectlyMarkedAsAPIObject struct { -} - -func (*MyIncorrectlyMarkedAsAPIObject) IsAnAPIObject() {} - -func TestResourceVersionerOfAPI(t *testing.T) { - type T struct { - Object - Expected string - } - testCases := map[string]T{ - "empty api object": {&MyAPIObject{}, ""}, - "api object with version": {&MyAPIObject{TypeMeta: TypeMeta{ResourceVersion: "1"}}, "1"}, - "pointer to api object with version": {&MyAPIObject{TypeMeta: TypeMeta{ResourceVersion: "1"}}, "1"}, - } - versioning := NewTypeMetaResourceVersioner() - for key, testCase := range testCases { - actual, err := versioning.ResourceVersion(testCase.Object) - if err != nil { - t.Errorf("%s: unexpected error %#v", key, err) - } - if actual != testCase.Expected { - t.Errorf("%s: expected %v, got %v", key, testCase.Expected, actual) - } - } - - failingCases := map[string]struct { - Object - Expected string - }{ - "not a valid object to try": {&MyIncorrectlyMarkedAsAPIObject{}, "1"}, - } - for key, testCase := range failingCases { - _, err := versioning.ResourceVersion(testCase.Object) - if err == nil { - t.Errorf("%s: expected error, got nil", key) - } - } - - setCases := map[string]struct { - Object - Expected string - }{ - "pointer to api object with version": {&MyAPIObject{TypeMeta: TypeMeta{ResourceVersion: "1"}}, "1"}, - } - for key, testCase := range setCases { - if err := versioning.SetResourceVersion(testCase.Object, "5"); err != nil { - t.Errorf("%s: unexpected error %#v", key, err) - } - actual, err := versioning.ResourceVersion(testCase.Object) - if err != nil { - t.Errorf("%s: unexpected error %#v", key, err) - } - if actual != "5" { - t.Errorf("%s: expected %v, got %v", key, "5", actual) - } - } -} - -func TestTypeMetaSelfLinker(t *testing.T) { - table := map[string]struct { - obj Object - expect string - try string - succeed bool - }{ - "normal": { - obj: &MyAPIObject{TypeMeta: TypeMeta{SelfLink: "foobar"}}, - expect: "foobar", - try: "newbar", - succeed: true, - }, - "fail": { - obj: &MyIncorrectlyMarkedAsAPIObject{}, - succeed: false, - }, - } - - linker := NewTypeMetaSelfLinker() - for name, item := range table { - got, err := linker.SelfLink(item.obj) - if e, a := item.succeed, err == nil; e != a { - t.Errorf("%v: expected %v, got %v", name, e, a) - } - if e, a := item.expect, got; item.succeed && e != a { - t.Errorf("%v: expected %v, got %v", name, e, a) - } - - err = linker.SetSelfLink(item.obj, item.try) - if e, a := item.succeed, err == nil; e != a { - t.Errorf("%v: expected %v, got %v", name, e, a) - } - if item.succeed { - got, err := linker.SelfLink(item.obj) - if err != nil { - t.Errorf("%v: expected no err, got %v", name, err) - } - if e, a := item.try, got; e != a { - t.Errorf("%v: expected %v, got %v", name, e, a) - } - } - } -} diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index f657861f7a8..c20c6a9df74 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -26,8 +26,8 @@ import ( // TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, // like this: // type MyAwesomeAPIObject struct { -// runtime.TypeMeta `yaml:",inline" json:",inline"` -// ... // other fields +// runtime.TypeMeta `yaml:",inline" json:",inline"` +// ... // other fields // } // func (*MyAwesomeAPIObject) IsAnAPIObject() {} // @@ -35,12 +35,14 @@ import ( // your own with the same fields. // type TypeMeta struct { - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + UID string `json:"uid,omitempty" yaml:"uid,omitempty"` CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` - APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` } // PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded diff --git a/pkg/tools/etcd_tools_test.go b/pkg/tools/etcd_tools_test.go index f899befb7c9..2ac4897cbf3 100644 --- a/pkg/tools/etcd_tools_test.go +++ b/pkg/tools/etcd_tools_test.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" @@ -44,7 +45,7 @@ func (*TestResource) IsAnAPIObject() {} var scheme *runtime.Scheme var codec runtime.Codec -var versioner = RuntimeVersionAdapter{runtime.NewTypeMetaResourceVersioner()} +var versioner = RuntimeVersionAdapter{meta.NewResourceVersioner()} func init() { scheme = runtime.NewScheme()