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	runtimetesting "k8s.io/apimachinery/pkg/runtime/testing"
29	"k8s.io/apimachinery/pkg/util/diff"
30)
31
32type testDecodable struct {
33	Other string
34	Value int `json:"value"`
35	gvk   schema.GroupVersionKind
36}
37
38func (d *testDecodable) GetObjectKind() schema.ObjectKind                { return d }
39func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
40func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind       { return d.gvk }
41func (d *testDecodable) DeepCopyObject() runtime.Object {
42	// no real deepcopy because these tests check for pointer equality
43	return d
44}
45
46type testNestedDecodable struct {
47	Other string
48	Value int `json:"value"`
49
50	gvk          schema.GroupVersionKind
51	nestedCalled bool
52	nestedErr    error
53}
54
55func (d *testNestedDecodable) GetObjectKind() schema.ObjectKind                { return d }
56func (d *testNestedDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
57func (d *testNestedDecodable) GroupVersionKind() schema.GroupVersionKind       { return d.gvk }
58func (d *testNestedDecodable) DeepCopyObject() runtime.Object {
59	// no real deepcopy because these tests check for pointer equality
60	return d
61}
62
63func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error {
64	d.nestedCalled = true
65	return d.nestedErr
66}
67
68func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error {
69	d.nestedCalled = true
70	return d.nestedErr
71}
72
73func TestNestedDecode(t *testing.T) {
74	n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")}
75	decoder := &mockSerializer{obj: n}
76	codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode")
77	if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr {
78		t.Errorf("unexpected error: %v", err)
79	}
80	if !n.nestedCalled {
81		t.Errorf("did not invoke nested decoder")
82	}
83}
84
85func TestNestedEncode(t *testing.T) {
86	n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")}
87	n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")}
88	encoder := &mockSerializer{obj: n}
89	codec := NewCodec(
90		encoder, nil,
91		&checkConvertor{obj: n2, groupVersion: schema.GroupVersion{Group: "other"}},
92		nil,
93		&mockTyper{gvks: []schema.GroupVersionKind{{Kind: "test"}}},
94		nil,
95		schema.GroupVersion{Group: "other"}, nil,
96		"TestNestedEncode",
97	)
98	if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr {
99		t.Errorf("unexpected error: %v", err)
100	}
101	if n.nestedCalled || !n2.nestedCalled {
102		t.Errorf("did not invoke correct nested decoder")
103	}
104}
105
106func TestNestedEncodeError(t *testing.T) {
107	n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to encode")}
108	gvk1 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v1"}
109	gvk2 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v2"}
110	n.SetGroupVersionKind(gvk1)
111	codec := NewCodec(
112		nil, nil,
113		&mockConvertor{},
114		nil,
115		&mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}},
116		nil,
117		schema.GroupVersion{Group: "other", Version: "v2"}, nil,
118		"TestNestedEncodeError",
119	)
120	if err := codec.Encode(n, ioutil.Discard); err != n.nestedErr {
121		t.Errorf("unexpected error: %v", err)
122	}
123	if n.GroupVersionKind() != gvk1 {
124		t.Errorf("unexpected gvk of input object: %v", n.GroupVersionKind())
125	}
126}
127
128func TestDecode(t *testing.T) {
129	gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}
130	decodable1 := &testDecodable{}
131	decodable2 := &testDecodable{}
132	decodable3 := &testDecodable{}
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		// decode into the same version as the serialized object
184		{
185			decodes: schema.GroupVersions{gvk1.GroupVersion()},
186
187			serializer:     &mockSerializer{actual: gvk1, obj: decodable1},
188			convertor:      &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}},
189			expectedGVK:    gvk1,
190			expectedObject: decodable1,
191		},
192	}
193
194	for i, test := range testCases {
195		t.Logf("%d", i)
196		s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i))
197		obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into)
198
199		if !reflect.DeepEqual(test.expectedGVK, gvk) {
200			t.Errorf("%d: unexpected GVK: %v", i, gvk)
201		}
202
203		switch {
204		case err == nil && test.errFn != nil:
205			t.Errorf("%d: failed: %v", i, err)
206			continue
207		case err != nil && test.errFn == nil:
208			t.Errorf("%d: failed: %v", i, err)
209			continue
210		case err != nil:
211			if !test.errFn(err) {
212				t.Errorf("%d: failed: %v", i, err)
213			}
214			if obj != nil {
215				t.Errorf("%d: should have returned nil object", i)
216			}
217			continue
218		}
219
220		if test.into != nil && test.into != obj {
221			t.Errorf("%d: expected into to be returned: %v", i, obj)
222			continue
223		}
224
225		switch {
226		case test.expectedObject != nil:
227			if !reflect.DeepEqual(test.expectedObject, obj) {
228				t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj))
229			}
230		case test.sameObject != nil:
231			if test.sameObject != obj {
232				t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj))
233			}
234		case obj != nil:
235			t.Errorf("%d: unexpected object: %#v", i, obj)
236		}
237	}
238}
239
240type checkConvertor struct {
241	err           error
242	in, obj       runtime.Object
243	groupVersion  runtime.GroupVersioner
244	directConvert bool
245}
246
247func (c *checkConvertor) Convert(in, out, context interface{}) error {
248	if !c.directConvert {
249		return fmt.Errorf("unexpected call to Convert")
250	}
251	if c.in != nil && c.in != in {
252		return fmt.Errorf("unexpected in: %s", in)
253	}
254	if c.obj != nil && c.obj != out {
255		return fmt.Errorf("unexpected out: %s", out)
256	}
257	return c.err
258}
259func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
260	if c.directConvert {
261		return nil, fmt.Errorf("unexpected call to ConvertToVersion")
262	}
263	if c.in != nil && c.in != in {
264		return nil, fmt.Errorf("unexpected in: %s", in)
265	}
266	if !reflect.DeepEqual(c.groupVersion, outVersion) {
267		return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion)
268	}
269	return c.obj, c.err
270}
271func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
272	return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
273}
274
275type mockConvertor struct {
276}
277
278func (c *mockConvertor) Convert(in, out, context interface{}) error {
279	return fmt.Errorf("unexpect call to Convert")
280}
281
282func (c *mockConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
283	objectKind := in.GetObjectKind()
284	inGVK := objectKind.GroupVersionKind()
285	if out, ok := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{inGVK}); ok {
286		objectKind.SetGroupVersionKind(out)
287	} else {
288		return nil, fmt.Errorf("unexpected conversion")
289	}
290	return in, nil
291}
292
293func (c *mockConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
294	return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
295}
296
297type mockSerializer struct {
298	err            error
299	obj            runtime.Object
300	encodingObjGVK schema.GroupVersionKind
301
302	defaults, actual *schema.GroupVersionKind
303	into             runtime.Object
304}
305
306func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
307	s.defaults = defaults
308	s.into = into
309	return s.obj, s.actual, s.err
310}
311
312func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error {
313	s.obj = obj
314	s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind()
315	return s.err
316}
317
318func (s *mockSerializer) Identifier() runtime.Identifier {
319	return runtime.Identifier("mock")
320}
321
322type mockCreater struct {
323	err error
324	obj runtime.Object
325}
326
327func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) {
328	return c.obj, c.err
329}
330
331type mockTyper struct {
332	gvks        []schema.GroupVersionKind
333	unversioned bool
334	err         error
335}
336
337func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
338	return t.gvks, t.unversioned, t.err
339}
340
341func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool {
342	return true
343}
344
345func TestDirectCodecEncode(t *testing.T) {
346	serializer := mockSerializer{}
347	typer := mockTyper{
348		gvks: []schema.GroupVersionKind{
349			{
350				Group: "wrong_group",
351				Kind:  "some_kind",
352			},
353			{
354				Group: "expected_group",
355				Kind:  "some_kind",
356			},
357		},
358	}
359
360	c := runtime.WithVersionEncoder{
361		Version:     schema.GroupVersion{Group: "expected_group"},
362		Encoder:     &serializer,
363		ObjectTyper: &typer,
364	}
365	c.Encode(&testDecodable{}, ioutil.Discard)
366	if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a {
367		t.Errorf("expected group to be %v, got %v", e, a)
368	}
369}
370
371func TestCacheableObject(t *testing.T) {
372	gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"}
373	gvk2 := schema.GroupVersionKind{Group: "group", Version: "version2", Kind: "MockCacheableObject"}
374
375	encoder := NewCodec(
376		&mockSerializer{}, &mockSerializer{},
377		&mockConvertor{}, nil,
378		&mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, nil,
379		gvk1.GroupVersion(), gvk2.GroupVersion(),
380		"TestCacheableObject")
381
382	runtimetesting.CacheableObjectTest(t, encoder)
383}
384
385func BenchmarkIdentifier(b *testing.B) {
386	encoder := &mockSerializer{}
387	gv := schema.GroupVersion{Group: "group", Version: "version"}
388
389	for i := 0; i < b.N; i++ {
390		id := identifier(gv, encoder)
391		// Avoid optimizing by compiler.
392		if id[0] != '{' {
393			b.Errorf("unexpected identifier: %s", id)
394		}
395	}
396}
397