...

Source file src/k8s.io/apimachinery/pkg/runtime/serializer/codec_test.go

Documentation: k8s.io/apimachinery/pkg/runtime/serializer

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package serializer
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"log"
    23  	"os"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/conversion"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	runtimetesting "k8s.io/apimachinery/pkg/runtime/testing"
    33  	"k8s.io/apimachinery/pkg/util/diff"
    34  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    35  
    36  	"github.com/google/go-cmp/cmp"
    37  	fuzz "github.com/google/gofuzz"
    38  	flag "github.com/spf13/pflag"
    39  	"sigs.k8s.io/yaml"
    40  )
    41  
    42  var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.")
    43  
    44  type testMetaFactory struct{}
    45  
    46  func (testMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
    47  	findKind := struct {
    48  		APIVersion string `json:"myVersionKey,omitempty"`
    49  		ObjectKind string `json:"myKindKey,omitempty"`
    50  	}{}
    51  	// yaml is a superset of json, so we use it to decode here. That way,
    52  	// we understand both.
    53  	if err := yaml.Unmarshal(data, &findKind); err != nil {
    54  		return nil, fmt.Errorf("couldn't get version/kind: %v", err)
    55  	}
    56  	gv, err := schema.ParseGroupVersion(findKind.APIVersion)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.ObjectKind}, nil
    61  }
    62  
    63  // TestObjectFuzzer can randomly populate all the above objects.
    64  var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
    65  	func(j *runtimetesting.MyWeirdCustomEmbeddedVersionKindField, c fuzz.Continue) {
    66  		c.FuzzNoCustom(j)
    67  		j.APIVersion = ""
    68  		j.ObjectKind = ""
    69  	},
    70  )
    71  
    72  // Returns a new Scheme set up with the test objects.
    73  func GetTestScheme() (*runtime.Scheme, runtime.Codec) {
    74  	internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal}
    75  	externalGV := schema.GroupVersion{Version: "v1"}
    76  	externalGV2 := schema.GroupVersion{Version: "v2"}
    77  
    78  	s := runtime.NewScheme()
    79  	// Ordinarily, we wouldn't add TestType2, but because this is a test and
    80  	// both types are from the same package, we need to get it into the system
    81  	// so that converter will match it with ExternalType2.
    82  	s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}, &runtimetesting.TestType2{}, &runtimetesting.ExternalInternalSame{})
    83  	s.AddKnownTypes(externalGV, &runtimetesting.ExternalInternalSame{})
    84  	s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{})
    85  	s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &runtimetesting.ExternalTestType2{})
    86  	s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{})
    87  	s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{})
    88  	s.AddKnownTypeWithName(externalGV2.WithKind("TestType1"), &runtimetesting.ExternalTestType1{})
    89  
    90  	s.AddUnversionedTypes(externalGV, &metav1.Status{})
    91  
    92  	utilruntime.Must(runtimetesting.RegisterConversions(s))
    93  
    94  	cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true}))
    95  	codec := cf.LegacyCodec(schema.GroupVersion{Version: "v1"})
    96  	return s, codec
    97  }
    98  
    99  var semantic = conversion.EqualitiesOrDie(
   100  	func(a, b runtimetesting.MyWeirdCustomEmbeddedVersionKindField) bool {
   101  		a.APIVersion, a.ObjectKind = "", ""
   102  		b.APIVersion, b.ObjectKind = "", ""
   103  		return a == b
   104  	},
   105  )
   106  
   107  func runTest(t *testing.T, source interface{}) {
   108  	name := reflect.TypeOf(source).Elem().Name()
   109  	TestObjectFuzzer.Fuzz(source)
   110  
   111  	_, codec := GetTestScheme()
   112  	data, err := runtime.Encode(codec, source.(runtime.Object))
   113  	if err != nil {
   114  		t.Errorf("%v: %v (%#v)", name, err, source)
   115  		return
   116  	}
   117  	obj2, err := runtime.Decode(codec, data)
   118  	if err != nil {
   119  		t.Errorf("%v: %v (%v)", name, err, string(data))
   120  		return
   121  	}
   122  	if !semantic.DeepEqual(source, obj2) {
   123  		t.Errorf("1: %v: diff: %v", name, diff.ObjectGoPrintSideBySide(source, obj2))
   124  		return
   125  	}
   126  	obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
   127  	if err := runtime.DecodeInto(codec, data, obj3.(runtime.Object)); err != nil {
   128  		t.Errorf("2: %v: %v", name, err)
   129  		return
   130  	}
   131  	if !semantic.DeepEqual(source, obj3) {
   132  		t.Errorf("3: %v: diff: %v", name, cmp.Diff(source, obj3))
   133  		return
   134  	}
   135  }
   136  
   137  func TestTypes(t *testing.T) {
   138  	table := []interface{}{
   139  		&runtimetesting.TestType1{},
   140  		&runtimetesting.ExternalInternalSame{},
   141  	}
   142  	for _, item := range table {
   143  		// Try a few times, since runTest uses random values.
   144  		for i := 0; i < *fuzzIters; i++ {
   145  			runTest(t, item)
   146  		}
   147  	}
   148  }
   149  
   150  func TestVersionedEncoding(t *testing.T) {
   151  	s, _ := GetTestScheme()
   152  	cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true}))
   153  	info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
   154  	encoder := info.Serializer
   155  
   156  	codec := cf.EncoderForVersion(encoder, schema.GroupVersion{Version: "v2"})
   157  	out, err := runtime.Encode(codec, &runtimetesting.TestType1{})
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	if string(out) != `{"myVersionKey":"v2","myKindKey":"TestType1"}`+"\n" {
   162  		t.Fatal(string(out))
   163  	}
   164  
   165  	codec = cf.EncoderForVersion(encoder, schema.GroupVersion{Version: "v3"})
   166  	_, err = runtime.Encode(codec, &runtimetesting.TestType1{})
   167  	if err == nil {
   168  		t.Fatal(err)
   169  	}
   170  
   171  	// unversioned encode with no versions is written directly to wire
   172  	codec = cf.EncoderForVersion(encoder, runtime.InternalGroupVersioner)
   173  	out, err = runtime.Encode(codec, &runtimetesting.TestType1{})
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	if string(out) != `{}`+"\n" {
   178  		t.Fatal(string(out))
   179  	}
   180  }
   181  
   182  func TestMultipleNames(t *testing.T) {
   183  	_, codec := GetTestScheme()
   184  
   185  	obj, _, err := codec.Decode([]byte(`{"myKindKey":"TestType3","myVersionKey":"v1","A":"value"}`), nil, nil)
   186  	if err != nil {
   187  		t.Fatalf("unexpected error: %v", err)
   188  	}
   189  	internal := obj.(*runtimetesting.TestType1)
   190  	if internal.A != "value" {
   191  		t.Fatalf("unexpected decoded object: %#v", internal)
   192  	}
   193  
   194  	out, err := runtime.Encode(codec, internal)
   195  	if err != nil {
   196  		t.Fatalf("unexpected error: %v", err)
   197  	}
   198  	if !strings.Contains(string(out), `"myKindKey":"TestType1"`) {
   199  		t.Errorf("unexpected encoded output: %s", string(out))
   200  	}
   201  }
   202  
   203  func TestStrictOption(t *testing.T) {
   204  	s, _ := GetTestScheme()
   205  	duplicateKeys := `{"myKindKey":"TestType3","myVersionKey":"v1","myVersionKey":"v1","A":"value"}`
   206  
   207  	strictCodec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})).LegacyCodec()
   208  	_, _, err := strictCodec.Decode([]byte(duplicateKeys), nil, nil)
   209  	if !runtime.IsStrictDecodingError(err) {
   210  		t.Fatalf("StrictDecodingError not returned on object with duplicate keys: %v, type: %v", err, reflect.TypeOf(err))
   211  	}
   212  
   213  	nonStrictCodec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: false})).LegacyCodec()
   214  	_, _, err = nonStrictCodec.Decode([]byte(duplicateKeys), nil, nil)
   215  	if runtime.IsStrictDecodingError(err) {
   216  		t.Fatalf("Non-Strict decoder returned a StrictDecodingError: %v", err)
   217  	}
   218  }
   219  
   220  func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) {
   221  	internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal}
   222  	externalGV := schema.GroupVersion{Version: "v1"}
   223  
   224  	s := runtime.NewScheme()
   225  	// create two names internally, with TestType1 being preferred
   226  	s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &runtimetesting.TestType1{})
   227  	s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &runtimetesting.TestType1{})
   228  	// create two names externally, with TestType1 being preferred
   229  	s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{})
   230  	s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &runtimetesting.ExternalTestType1{})
   231  	if err := runtimetesting.RegisterConversions(s); err != nil {
   232  		t.Fatalf("unexpected error; %v", err)
   233  	}
   234  
   235  	ext := &runtimetesting.ExternalTestType1{}
   236  	ext.APIVersion = "v1"
   237  	ext.ObjectKind = "OtherType1"
   238  	ext.A = "test"
   239  	data, err := json.Marshal(ext)
   240  	if err != nil {
   241  		t.Fatalf("unexpected error: %v", err)
   242  	}
   243  	expect := &runtimetesting.TestType1{A: "test"}
   244  
   245  	codec := newCodecFactory(
   246  		s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true}),
   247  	).LegacyCodec(schema.GroupVersion{Version: "v1"})
   248  
   249  	obj, err := runtime.Decode(codec, data)
   250  	if err != nil {
   251  		t.Fatalf("unexpected error: %v", err)
   252  	}
   253  	if !semantic.DeepEqual(expect, obj) {
   254  		t.Errorf("unexpected object: %#v", obj)
   255  	}
   256  
   257  	into := &runtimetesting.TestType1{}
   258  	if err := runtime.DecodeInto(codec, data, into); err != nil {
   259  		t.Fatalf("unexpected error: %v", err)
   260  	}
   261  	if !semantic.DeepEqual(expect, into) {
   262  		t.Errorf("unexpected object: %#v", obj)
   263  	}
   264  }
   265  
   266  func TestEncode_Ptr(t *testing.T) {
   267  	_, codec := GetTestScheme()
   268  	tt := &runtimetesting.TestType1{A: "I am a pointer object"}
   269  	data, err := runtime.Encode(codec, tt)
   270  	obj2, err2 := runtime.Decode(codec, data)
   271  	if err != nil || err2 != nil {
   272  		t.Fatalf("Failure: '%v' '%v'\n%s", err, err2, data)
   273  	}
   274  	if _, ok := obj2.(*runtimetesting.TestType1); !ok {
   275  		t.Fatalf("Got wrong type")
   276  	}
   277  	if !semantic.DeepEqual(obj2, tt) {
   278  		t.Errorf("Expected:\n %#v,\n Got:\n %#v", tt, obj2)
   279  	}
   280  }
   281  
   282  func TestBadJSONRejection(t *testing.T) {
   283  	log.SetOutput(os.Stderr)
   284  	_, codec := GetTestScheme()
   285  	badJSONs := [][]byte{
   286  		[]byte(`{"myVersionKey":"v1"}`),                          // Missing kind
   287  		[]byte(`{"myVersionKey":"v1","myKindKey":"bar"}`),        // Unknown kind
   288  		[]byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version
   289  		[]byte(`{"myKindKey":"TestType1"}`),                      // Missing version
   290  	}
   291  	for _, b := range badJSONs {
   292  		if _, err := runtime.Decode(codec, b); err == nil {
   293  			t.Errorf("Did not reject bad json: %s", string(b))
   294  		}
   295  	}
   296  	badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`)
   297  	if err := runtime.DecodeInto(codec, badJSONKindMismatch, &runtimetesting.TestType1{}); err == nil {
   298  		t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
   299  	}
   300  	if err := runtime.DecodeInto(codec, []byte(``), &runtimetesting.TestType1{}); err != nil {
   301  		t.Errorf("Should allow empty decode: %v", err)
   302  	}
   303  	if _, _, err := codec.Decode([]byte(``), &schema.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err == nil {
   304  		t.Errorf("Did not give error for empty data with only kind default")
   305  	}
   306  	if _, _, err := codec.Decode([]byte(`{"myVersionKey":"v1"}`), &schema.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err != nil {
   307  		t.Errorf("Gave error for version and kind default")
   308  	}
   309  	if _, _, err := codec.Decode([]byte(`{"myKindKey":"ExternalInternalSame"}`), &schema.GroupVersionKind{Version: "v1"}, nil); err != nil {
   310  		t.Errorf("Gave error for version and kind default")
   311  	}
   312  	if _, _, err := codec.Decode([]byte(``), &schema.GroupVersionKind{Kind: "ExternalInternalSame", Version: "v1"}, nil); err != nil {
   313  		t.Errorf("Gave error for version and kind defaulted: %v", err)
   314  	}
   315  	if _, err := runtime.Decode(codec, []byte(``)); err == nil {
   316  		t.Errorf("Did not give error for empty data")
   317  	}
   318  }
   319  
   320  // Returns a new Scheme set up with the test objects needed by TestDirectCodec.
   321  func GetDirectCodecTestScheme() *runtime.Scheme {
   322  	internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal}
   323  	externalGV := schema.GroupVersion{Version: "v1"}
   324  
   325  	s := runtime.NewScheme()
   326  	// Ordinarily, we wouldn't add TestType2, but because this is a test and
   327  	// both types are from the same package, we need to get it into the system
   328  	// so that converter will match it with ExternalType2.
   329  	s.AddKnownTypes(internalGV, &runtimetesting.TestType1{})
   330  	s.AddKnownTypes(externalGV, &runtimetesting.ExternalTestType1{})
   331  
   332  	s.AddUnversionedTypes(externalGV, &metav1.Status{})
   333  
   334  	utilruntime.Must(runtimetesting.RegisterConversions(s))
   335  	return s
   336  }
   337  
   338  func TestDirectCodec(t *testing.T) {
   339  	s := GetDirectCodecTestScheme()
   340  	cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true}))
   341  	info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
   342  	serializer := info.Serializer
   343  	df := cf.WithoutConversion()
   344  	ignoredGV, err := schema.ParseGroupVersion("ignored group/ignored version")
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	directEncoder := df.EncoderForVersion(serializer, ignoredGV)
   349  	directDecoder := df.DecoderToVersion(serializer, ignoredGV)
   350  	out, err := runtime.Encode(directEncoder, &runtimetesting.ExternalTestType1{})
   351  	if err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	if string(out) != `{"myVersionKey":"v1","myKindKey":"ExternalTestType1"}`+"\n" {
   355  		t.Fatal(string(out))
   356  	}
   357  	a, _, err := directDecoder.Decode(out, nil, nil)
   358  	if err != nil {
   359  		t.Fatalf("error on Decode: %v", err)
   360  	}
   361  	e := &runtimetesting.ExternalTestType1{
   362  		MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{
   363  			APIVersion: "v1",
   364  			ObjectKind: "ExternalTestType1",
   365  		},
   366  	}
   367  	if !semantic.DeepEqual(e, a) {
   368  		t.Fatalf("expect %v, got %v", e, a)
   369  	}
   370  }
   371  

View as plain text