Merge pull request #1969 from smarterclayton/new_typemeta
Allow Meta lookup to work across ObjectType and ListMeta
This commit is contained in:
commit
e46af6e37f
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
19
pkg/api/meta/doc.go
Normal file
19
pkg/api/meta/doc.go
Normal file
@ -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
|
281
pkg/api/meta/meta.go
Normal file
281
pkg/api/meta/meta.go
Normal file
@ -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
|
||||
}
|
355
pkg/api/meta/meta_test.go
Normal file
355
pkg/api/meta/meta_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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":
|
||||
|
19
pkg/client/cache/reflector.go
vendored
19
pkg/client/cache/reflector.go
vendored
@ -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++
|
||||
}
|
||||
|
||||
|
@ -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`,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user