1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package versioning
18
19import (
20	"fmt"
21	"io"
22	"io/ioutil"
23	"reflect"
24	"testing"
25
26	"k8s.io/apimachinery/pkg/runtime"
27	"k8s.io/apimachinery/pkg/runtime/schema"
28	"k8s.io/apimachinery/pkg/util/diff"
29)
30
31type testDecodable struct {
32	Other string
33	Value int `json:"value"`
34	gvk   schema.GroupVersionKind
35}
36
37func (d *testDecodable) GetObjectKind() schema.ObjectKind                { return d }
38func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
39func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind       { return d.gvk }
40func (d *testDecodable) DeepCopyObject() runtime.Object {
41	// no real deepcopy because these tests check for pointer equality
42	return d
43}
44
45type testNestedDecodable struct {
46	Other string
47	Value int `json:"value"`
48
49	gvk          schema.GroupVersionKind
50	nestedCalled bool
51	nestedErr    error
52}
53
54func (d *testNestedDecodable) GetObjectKind() schema.ObjectKind                { return d }
55func (d *testNestedDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
56func (d *testNestedDecodable) GroupVersionKind() schema.GroupVersionKind       { return d.gvk }
57func (d *testNestedDecodable) DeepCopyObject() runtime.Object {
58	// no real deepcopy because these tests check for pointer equality
59	return d
60}
61
62func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error {
63	d.nestedCalled = true
64	return d.nestedErr
65}
66
67func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error {
68	d.nestedCalled = true
69	return d.nestedErr
70}
71
72func TestNestedDecode(t *testing.T) {
73	n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")}
74	decoder := &mockSerializer{obj: n}
75	codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode")
76	if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr {
77		t.Errorf("unexpected error: %v", err)
78	}
79	if !n.nestedCalled {
80		t.Errorf("did not invoke nested decoder")
81	}
82}
83
84func TestNestedEncode(t *testing.T) {
85	n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")}
86	n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")}
87	encoder := &mockSerializer{obj: n}
88	codec := NewCodec(
89		encoder, nil,
90		&checkConvertor{obj: n2, groupVersion: schema.GroupVersion{Group: "other"}},
91		nil,
92		&mockTyper{gvks: []schema.GroupVersionKind{{Kind: "test"}}},
93		nil,
94		schema.GroupVersion{Group: "other"}, nil,
95		"TestNestedEncode",
96	)
97	if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr {
98		t.Errorf("unexpected error: %v", err)
99	}
100	if n.nestedCalled || !n2.nestedCalled {
101		t.Errorf("did not invoke correct nested decoder")
102	}
103}
104
105func TestNestedEncodeError(t *testing.T) {
106	n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to encode")}
107	gvk1 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v1"}
108	gvk2 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v2"}
109	n.SetGroupVersionKind(gvk1)
110	codec := NewCodec(
111		nil, nil,
112		&mockConvertor{},
113		nil,
114		&mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}},
115		nil,
116		schema.GroupVersion{Group: "other", Version: "v2"}, nil,
117		"TestNestedEncodeError",
118	)
119	if err := codec.Encode(n, ioutil.Discard); err != n.nestedErr {
120		t.Errorf("unexpected error: %v", err)
121	}
122	if n.GroupVersionKind() != gvk1 {
123		t.Errorf("unexpected gvk of input object: %v", n.GroupVersionKind())
124	}
125}
126
127func TestDecode(t *testing.T) {
128	gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}
129	decodable1 := &testDecodable{}
130	decodable2 := &testDecodable{}
131	decodable3 := &testDecodable{}
132	versionedDecodable1 := &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}
133
134	testCases := []struct {
135		serializer runtime.Serializer
136		convertor  runtime.ObjectConvertor
137		creater    runtime.ObjectCreater
138		typer      runtime.ObjectTyper
139		defaulter  runtime.ObjectDefaulter
140		yaml       bool
141		pretty     bool
142
143		encodes, decodes runtime.GroupVersioner
144
145		defaultGVK *schema.GroupVersionKind
146		into       runtime.Object
147
148		errFn          func(error) bool
149		expectedObject runtime.Object
150		sameObject     runtime.Object
151		expectedGVK    *schema.GroupVersionKind
152	}{
153		{
154			serializer:  &mockSerializer{actual: gvk1},
155			convertor:   &checkConvertor{groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}},
156			expectedGVK: gvk1,
157			decodes:     schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal},
158		},
159		{
160			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
161			convertor:   &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}},
162			expectedGVK: gvk1,
163			sameObject:  decodable2,
164			decodes:     schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal},
165		},
166		// defaultGVK.Group is allowed to force a conversion to the destination group
167		{
168			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
169			defaultGVK:  &schema.GroupVersionKind{Group: "force"},
170			convertor:   &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}},
171			expectedGVK: gvk1,
172			sameObject:  decodable2,
173			decodes:     schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal},
174		},
175		// uses direct conversion for into when objects differ
176		{
177			into:        decodable3,
178			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
179			convertor:   &checkConvertor{in: decodable1, obj: decodable3, directConvert: true},
180			expectedGVK: gvk1,
181			sameObject:  decodable3,
182		},
183		{
184			into:        versionedDecodable1,
185			serializer:  &mockSerializer{actual: gvk1, obj: decodable3},
186			convertor:   &checkConvertor{in: decodable3, obj: decodable1, directConvert: true},
187			expectedGVK: gvk1,
188			sameObject:  versionedDecodable1,
189		},
190		// returns directly when serializer returns into
191		{
192			into:        decodable3,
193			serializer:  &mockSerializer{actual: gvk1, obj: decodable3},
194			expectedGVK: gvk1,
195			sameObject:  decodable3,
196		},
197		// returns directly when serializer returns into
198		{
199			into:        versionedDecodable1,
200			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
201			expectedGVK: gvk1,
202			sameObject:  versionedDecodable1,
203		},
204
205		// runtime.VersionedObjects are decoded
206		{
207			into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
208
209			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
210			convertor:      &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}},
211			expectedGVK:    gvk1,
212			expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}},
213			decodes:        schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal},
214		},
215
216		// decode into the same version as the serialized object
217		{
218			decodes: schema.GroupVersions{gvk1.GroupVersion()},
219
220			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
221			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}},
222			expectedGVK:    gvk1,
223			expectedObject: decodable1,
224		},
225		{
226			into:    &runtime.VersionedObjects{Objects: []runtime.Object{}},
227			decodes: schema.GroupVersions{gvk1.GroupVersion()},
228
229			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
230			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}},
231			expectedGVK:    gvk1,
232			expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
233		},
234
235		// codec with non matching version skips conversion altogether
236		{
237			decodes: schema.GroupVersions{{Group: "something", Version: "else"}},
238
239			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
240			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}},
241			expectedGVK:    gvk1,
242			expectedObject: decodable1,
243		},
244		{
245			into:    &runtime.VersionedObjects{Objects: []runtime.Object{}},
246			decodes: schema.GroupVersions{{Group: "something", Version: "else"}},
247
248			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
249			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}},
250			expectedGVK:    gvk1,
251			expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
252		},
253	}
254
255	for i, test := range testCases {
256		t.Logf("%d", i)
257		s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i))
258		obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into)
259
260		if !reflect.DeepEqual(test.expectedGVK, gvk) {
261			t.Errorf("%d: unexpected GVK: %v", i, gvk)
262		}
263
264		switch {
265		case err == nil && test.errFn != nil:
266			t.Errorf("%d: failed: %v", i, err)
267			continue
268		case err != nil && test.errFn == nil:
269			t.Errorf("%d: failed: %v", i, err)
270			continue
271		case err != nil:
272			if !test.errFn(err) {
273				t.Errorf("%d: failed: %v", i, err)
274			}
275			if obj != nil {
276				t.Errorf("%d: should have returned nil object", i)
277			}
278			continue
279		}
280
281		if test.into != nil && test.into != obj {
282			t.Errorf("%d: expected into to be returned: %v", i, obj)
283			continue
284		}
285
286		switch {
287		case test.expectedObject != nil:
288			if !reflect.DeepEqual(test.expectedObject, obj) {
289				t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj))
290			}
291		case test.sameObject != nil:
292			if test.sameObject != obj {
293				t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj))
294			}
295		case obj != nil:
296			t.Errorf("%d: unexpected object: %#v", i, obj)
297		}
298	}
299}
300
301type checkConvertor struct {
302	err           error
303	in, obj       runtime.Object
304	groupVersion  runtime.GroupVersioner
305	directConvert bool
306}
307
308func (c *checkConvertor) Convert(in, out, context interface{}) error {
309	if !c.directConvert {
310		return fmt.Errorf("unexpected call to Convert")
311	}
312	if c.in != nil && c.in != in {
313		return fmt.Errorf("unexpected in: %s", in)
314	}
315	if c.obj != nil && c.obj != out {
316		return fmt.Errorf("unexpected out: %s", out)
317	}
318	return c.err
319}
320func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
321	if c.directConvert {
322		return nil, fmt.Errorf("unexpected call to ConvertToVersion")
323	}
324	if c.in != nil && c.in != in {
325		return nil, fmt.Errorf("unexpected in: %s", in)
326	}
327	if !reflect.DeepEqual(c.groupVersion, outVersion) {
328		return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion)
329	}
330	return c.obj, c.err
331}
332func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
333	return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
334}
335
336type mockConvertor struct {
337}
338
339func (c *mockConvertor) Convert(in, out, context interface{}) error {
340	return fmt.Errorf("unexpect call to Convert")
341}
342
343func (c *mockConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
344	objectKind := in.GetObjectKind()
345	inGVK := objectKind.GroupVersionKind()
346	if out, ok := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{inGVK}); ok {
347		objectKind.SetGroupVersionKind(out)
348	} else {
349		return nil, fmt.Errorf("unexpected conversion")
350	}
351	return in, nil
352}
353
354func (c *mockConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
355	return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
356}
357
358type mockSerializer struct {
359	err            error
360	obj            runtime.Object
361	encodingObjGVK schema.GroupVersionKind
362
363	defaults, actual *schema.GroupVersionKind
364	into             runtime.Object
365}
366
367func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
368	s.defaults = defaults
369	s.into = into
370	return s.obj, s.actual, s.err
371}
372
373func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error {
374	s.obj = obj
375	s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind()
376	return s.err
377}
378
379type mockCreater struct {
380	err error
381	obj runtime.Object
382}
383
384func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) {
385	return c.obj, c.err
386}
387
388type mockTyper struct {
389	gvks        []schema.GroupVersionKind
390	unversioned bool
391	err         error
392}
393
394func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
395	return t.gvks, t.unversioned, t.err
396}
397
398func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool {
399	return true
400}
401
402func TestDirectCodecEncode(t *testing.T) {
403	serializer := mockSerializer{}
404	typer := mockTyper{
405		gvks: []schema.GroupVersionKind{
406			{
407				Group: "wrong_group",
408				Kind:  "some_kind",
409			},
410			{
411				Group: "expected_group",
412				Kind:  "some_kind",
413			},
414		},
415	}
416
417	c := runtime.WithVersionEncoder{
418		Version:     schema.GroupVersion{Group: "expected_group"},
419		Encoder:     &serializer,
420		ObjectTyper: &typer,
421	}
422	c.Encode(&testDecodable{}, ioutil.Discard)
423	if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a {
424		t.Errorf("expected group to be %v, got %v", e, a)
425	}
426}
427