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 TestDecode(t *testing.T) {
106	gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}
107	decodable1 := &testDecodable{}
108	decodable2 := &testDecodable{}
109	decodable3 := &testDecodable{}
110	versionedDecodable1 := &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}
111
112	testCases := []struct {
113		serializer runtime.Serializer
114		convertor  runtime.ObjectConvertor
115		creater    runtime.ObjectCreater
116		typer      runtime.ObjectTyper
117		defaulter  runtime.ObjectDefaulter
118		yaml       bool
119		pretty     bool
120
121		encodes, decodes runtime.GroupVersioner
122
123		defaultGVK *schema.GroupVersionKind
124		into       runtime.Object
125
126		errFn          func(error) bool
127		expectedObject runtime.Object
128		sameObject     runtime.Object
129		expectedGVK    *schema.GroupVersionKind
130	}{
131		{
132			serializer:  &mockSerializer{actual: gvk1},
133			convertor:   &checkConvertor{groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}},
134			expectedGVK: gvk1,
135			decodes:     schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal},
136		},
137		{
138			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
139			convertor:   &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}},
140			expectedGVK: gvk1,
141			sameObject:  decodable2,
142			decodes:     schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal},
143		},
144		// defaultGVK.Group is allowed to force a conversion to the destination group
145		{
146			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
147			defaultGVK:  &schema.GroupVersionKind{Group: "force"},
148			convertor:   &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}},
149			expectedGVK: gvk1,
150			sameObject:  decodable2,
151			decodes:     schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal},
152		},
153		// uses direct conversion for into when objects differ
154		{
155			into:        decodable3,
156			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
157			convertor:   &checkConvertor{in: decodable1, obj: decodable3, directConvert: true},
158			expectedGVK: gvk1,
159			sameObject:  decodable3,
160		},
161		{
162			into:        versionedDecodable1,
163			serializer:  &mockSerializer{actual: gvk1, obj: decodable3},
164			convertor:   &checkConvertor{in: decodable3, obj: decodable1, directConvert: true},
165			expectedGVK: gvk1,
166			sameObject:  versionedDecodable1,
167		},
168		// returns directly when serializer returns into
169		{
170			into:        decodable3,
171			serializer:  &mockSerializer{actual: gvk1, obj: decodable3},
172			expectedGVK: gvk1,
173			sameObject:  decodable3,
174		},
175		// returns directly when serializer returns into
176		{
177			into:        versionedDecodable1,
178			serializer:  &mockSerializer{actual: gvk1, obj: decodable1},
179			expectedGVK: gvk1,
180			sameObject:  versionedDecodable1,
181		},
182
183		// runtime.VersionedObjects are decoded
184		{
185			into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
186
187			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
188			convertor:      &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}},
189			expectedGVK:    gvk1,
190			expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}},
191			decodes:        schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal},
192		},
193
194		// decode into the same version as the serialized object
195		{
196			decodes: schema.GroupVersions{gvk1.GroupVersion()},
197
198			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
199			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}},
200			expectedGVK:    gvk1,
201			expectedObject: decodable1,
202		},
203		{
204			into:    &runtime.VersionedObjects{Objects: []runtime.Object{}},
205			decodes: schema.GroupVersions{gvk1.GroupVersion()},
206
207			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
208			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}},
209			expectedGVK:    gvk1,
210			expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
211		},
212
213		// codec with non matching version skips conversion altogether
214		{
215			decodes: schema.GroupVersions{{Group: "something", Version: "else"}},
216
217			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
218			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}},
219			expectedGVK:    gvk1,
220			expectedObject: decodable1,
221		},
222		{
223			into:    &runtime.VersionedObjects{Objects: []runtime.Object{}},
224			decodes: schema.GroupVersions{{Group: "something", Version: "else"}},
225
226			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
227			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}},
228			expectedGVK:    gvk1,
229			expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
230		},
231	}
232
233	for i, test := range testCases {
234		t.Logf("%d", i)
235		s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i))
236		obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into)
237
238		if !reflect.DeepEqual(test.expectedGVK, gvk) {
239			t.Errorf("%d: unexpected GVK: %v", i, gvk)
240		}
241
242		switch {
243		case err == nil && test.errFn != nil:
244			t.Errorf("%d: failed: %v", i, err)
245			continue
246		case err != nil && test.errFn == nil:
247			t.Errorf("%d: failed: %v", i, err)
248			continue
249		case err != nil:
250			if !test.errFn(err) {
251				t.Errorf("%d: failed: %v", i, err)
252			}
253			if obj != nil {
254				t.Errorf("%d: should have returned nil object", i)
255			}
256			continue
257		}
258
259		if test.into != nil && test.into != obj {
260			t.Errorf("%d: expected into to be returned: %v", i, obj)
261			continue
262		}
263
264		switch {
265		case test.expectedObject != nil:
266			if !reflect.DeepEqual(test.expectedObject, obj) {
267				t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj))
268			}
269		case test.sameObject != nil:
270			if test.sameObject != obj {
271				t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj))
272			}
273		case obj != nil:
274			t.Errorf("%d: unexpected object: %#v", i, obj)
275		}
276	}
277}
278
279type checkConvertor struct {
280	err           error
281	in, obj       runtime.Object
282	groupVersion  runtime.GroupVersioner
283	directConvert bool
284}
285
286func (c *checkConvertor) Convert(in, out, context interface{}) error {
287	if !c.directConvert {
288		return fmt.Errorf("unexpected call to Convert")
289	}
290	if c.in != nil && c.in != in {
291		return fmt.Errorf("unexpected in: %s", in)
292	}
293	if c.obj != nil && c.obj != out {
294		return fmt.Errorf("unexpected out: %s", out)
295	}
296	return c.err
297}
298func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
299	if c.directConvert {
300		return nil, fmt.Errorf("unexpected call to ConvertToVersion")
301	}
302	if c.in != nil && c.in != in {
303		return nil, fmt.Errorf("unexpected in: %s", in)
304	}
305	if !reflect.DeepEqual(c.groupVersion, outVersion) {
306		return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion)
307	}
308	return c.obj, c.err
309}
310func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
311	return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
312}
313
314type mockSerializer struct {
315	err            error
316	obj            runtime.Object
317	encodingObjGVK schema.GroupVersionKind
318
319	defaults, actual *schema.GroupVersionKind
320	into             runtime.Object
321}
322
323func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
324	s.defaults = defaults
325	s.into = into
326	return s.obj, s.actual, s.err
327}
328
329func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error {
330	s.obj = obj
331	s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind()
332	return s.err
333}
334
335type mockCreater struct {
336	err error
337	obj runtime.Object
338}
339
340func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) {
341	return c.obj, c.err
342}
343
344type mockTyper struct {
345	gvks        []schema.GroupVersionKind
346	unversioned bool
347	err         error
348}
349
350func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
351	return t.gvks, t.unversioned, t.err
352}
353
354func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool {
355	return true
356}
357
358func TestDirectCodecEncode(t *testing.T) {
359	serializer := mockSerializer{}
360	typer := mockTyper{
361		gvks: []schema.GroupVersionKind{
362			{
363				Group: "wrong_group",
364				Kind:  "some_kind",
365			},
366			{
367				Group: "expected_group",
368				Kind:  "some_kind",
369			},
370		},
371	}
372
373	c := DirectEncoder{
374		Version:     schema.GroupVersion{Group: "expected_group"},
375		Encoder:     &serializer,
376		ObjectTyper: &typer,
377	}
378	c.Encode(&testDecodable{}, ioutil.Discard)
379	if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a {
380		t.Errorf("expected group to be %v, got %v", e, a)
381	}
382}
383