let encoder handle unversioned objects

address lavalamp's comments

address lavalamp's comments

address lavalamp's comments

add a TODO based on lavalamp's comment

add a TODO based on lavalamp's comment
This commit is contained in:
Chao Xu 2015-09-18 14:44:08 -07:00
parent 9537c43a62
commit ab5c1f6710
4 changed files with 152 additions and 0 deletions

View File

@ -19,6 +19,7 @@ package conversion
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path"
) )
// EncodeToVersion turns the given api object into an appropriate JSON string. // EncodeToVersion turns the given api object into an appropriate JSON string.
@ -52,6 +53,14 @@ import (
func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) { func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) {
obj = maybeCopy(obj) obj = maybeCopy(obj)
v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer
// Don't encode an object defined in the unversioned package, unless if the
// destVersion is v1, encode it to v1 for backward compatibility.
pkg := path.Base(v.Type().PkgPath())
if pkg == "unversioned" && destVersion != "v1" {
return s.encodeUnversionedObject(obj)
}
if _, registered := s.typeToVersion[v.Type()]; !registered { if _, registered := s.typeToVersion[v.Type()]; !registered {
return nil, fmt.Errorf("type %v is not registered for %q and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type(), destVersion) return nil, fmt.Errorf("type %v is not registered for %q and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type(), destVersion)
} }
@ -102,3 +111,21 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
return data, nil return data, nil
} }
func (s *Scheme) encodeUnversionedObject(obj interface{}) (data []byte, err error) {
_, objKind, err := s.ObjectVersionAndKind(obj)
if err != nil {
return nil, err
}
if err = s.SetVersionAndKind("", objKind, obj); err != nil {
return nil, err
}
data, err = json.Marshal(obj)
if err != nil {
return nil, err
}
// Version and Kind should be blank in memory. Reset them, since it's
// possible that we modified a user object and not a copy above.
err = s.SetVersionAndKind("", "", obj)
return data, nil
}

View File

@ -19,6 +19,7 @@ package conversion
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path"
"reflect" "reflect"
) )
@ -77,6 +78,7 @@ func UpdateVersionAndKind(baseFields []string, versionField, version, kindField,
if err != nil { if err != nil {
return err return err
} }
pkg := path.Base(v.Type().PkgPath())
t := v.Type() t := v.Type()
name := t.Name() name := t.Name()
if v.Kind() != reflect.Struct { if v.Kind() != reflect.Struct {
@ -93,6 +95,15 @@ func UpdateVersionAndKind(baseFields []string, versionField, version, kindField,
field := v.FieldByName(kindField) field := v.FieldByName(kindField)
if !field.IsValid() { if !field.IsValid() {
// Types defined in the unversioned package are allowed to not have a
// kindField. Clients will have to know what they are based on the
// context.
// TODO: add some type trait here, or some way of indicating whether
// this feature is allowed on a per-type basis. Using package name is
// overly broad and a bit hacky.
if pkg == "unversioned" {
return nil
}
return fmt.Errorf("couldn't find %v field in %#v", kindField, v.Interface()) return fmt.Errorf("couldn't find %v field in %#v", kindField, v.Interface())
} }
field.SetString(kind) field.SetString(kind)

View File

@ -17,8 +17,11 @@ limitations under the License.
package conversion package conversion
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
"k8s.io/kubernetes/pkg/api/unversioned"
) )
func TestSimpleMetaFactoryInterpret(t *testing.T) { func TestSimpleMetaFactoryInterpret(t *testing.T) {
@ -72,6 +75,25 @@ func TestSimpleMetaFactoryUpdate(t *testing.T) {
} }
} }
// Test Updating objects that don't have a Kind field.
func TestSimpleMetaFactoryUpdateNoKindField(t *testing.T) {
factory := SimpleMetaFactory{VersionField: "APIVersion", KindField: "Kind"}
// obj does not have a Kind field and is not defined in the unversioned package.
obj := struct {
SomeField string
}{"1"}
expectedError := fmt.Errorf("couldn't find %v field in %#v", factory.KindField, obj)
if err := factory.Update("test", "other", &obj); err == nil || expectedError.Error() != err.Error() {
t.Fatalf("expected error: %v, got: %v", expectedError, err)
}
// ListMeta does not have a Kind field, but is defined in the unversioned package.
listMeta := unversioned.ListMeta{}
if err := factory.Update("test", "other", &listMeta); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestSimpleMetaFactoryUpdateStruct(t *testing.T) { func TestSimpleMetaFactoryUpdateStruct(t *testing.T) {
factory := SimpleMetaFactory{BaseFields: []string{"Test"}, VersionField: "V", KindField: "K"} factory := SimpleMetaFactory{BaseFields: []string{"Test"}, VersionField: "V", KindField: "K"}

View File

@ -0,0 +1,92 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion_test
import (
"encoding/json"
"reflect"
"testing"
// TODO: Ideally we should create the necessary package structure in e.g.,
// pkg/conversion/test/... instead of importing pkg/api here.
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
var status = &unversioned.Status{
Status: unversioned.StatusFailure,
Code: 200,
Reason: unversioned.StatusReasonUnknown,
Message: "",
}
func TestV1EncodeDecodeStatus(t *testing.T) {
v1Codec := testapi.Default.Codec()
encoded, err := v1Codec.Encode(status)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
typeMeta := unversioned.TypeMeta{}
if err := json.Unmarshal(encoded, &typeMeta); err != nil {
t.Errorf("unexpected error: %v", err)
}
if typeMeta.Kind != "Status" {
t.Errorf("Kind is not set to \"Status\". Got %v", string(encoded))
}
if typeMeta.APIVersion != "v1" {
t.Errorf("APIVersion is not set to \"v1\". Got %v", string(encoded))
}
decoded, err := v1Codec.Decode(encoded)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(status, decoded) {
t.Errorf("expected: %v, got: %v", status, decoded)
}
}
func TestExperimentalEncodeDecodeStatus(t *testing.T) {
// TODO: caesarxuchao: use the testapi.Experimental.Codec() once the PR that
// moves experimental from v1 to v1alpha1 got merged.
// expCodec := testapi.Experimental.Codec()
expCodec := runtime.CodecFor(api.Scheme, "experimental/v1alpha1")
encoded, err := expCodec.Encode(status)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
typeMeta := unversioned.TypeMeta{}
if err := json.Unmarshal(encoded, &typeMeta); err != nil {
t.Errorf("unexpected error: %v", err)
}
if typeMeta.Kind != "Status" {
t.Errorf("Kind is not set to \"Status\". Got %s", encoded)
}
if typeMeta.APIVersion != "" {
t.Errorf("APIVersion is not set to \"\". Got %s", encoded)
}
decoded, err := expCodec.Decode(encoded)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(status, decoded) {
t.Errorf("expected: %v, got: %v", status, decoded)
}
}