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 mockTyper struct {
323	gvks        []schema.GroupVersionKind
324	unversioned bool
325	err         error
326}
327
328func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
329	return t.gvks, t.unversioned, t.err
330}
331
332func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool {
333	return true
334}
335
336func TestDirectCodecEncode(t *testing.T) {
337	serializer := mockSerializer{}
338	typer := mockTyper{
339		gvks: []schema.GroupVersionKind{
340			{
341				Group: "wrong_group",
342				Kind:  "some_kind",
343			},
344			{
345				Group: "expected_group",
346				Kind:  "some_kind",
347			},
348		},
349	}
350
351	c := runtime.WithVersionEncoder{
352		Version:     schema.GroupVersion{Group: "expected_group"},
353		Encoder:     &serializer,
354		ObjectTyper: &typer,
355	}
356	c.Encode(&testDecodable{}, ioutil.Discard)
357	if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a {
358		t.Errorf("expected group to be %v, got %v", e, a)
359	}
360}
361
362func TestCacheableObject(t *testing.T) {
363	gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"}
364	gvk2 := schema.GroupVersionKind{Group: "group", Version: "version2", Kind: "MockCacheableObject"}
365
366	encoder := NewCodec(
367		&mockSerializer{}, &mockSerializer{},
368		&mockConvertor{}, nil,
369		&mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, nil,
370		gvk1.GroupVersion(), gvk2.GroupVersion(),
371		"TestCacheableObject")
372
373	runtimetesting.CacheableObjectTest(t, encoder)
374}
375
376func BenchmarkIdentifier(b *testing.B) {
377	encoder := &mockSerializer{}
378	gv := schema.GroupVersion{Group: "group", Version: "version"}
379
380	for i := 0; i < b.N; i++ {
381		id := identifier(gv, encoder)
382		// Avoid optimizing by compiler.
383		if id[0] != '{' {
384			b.Errorf("unexpected identifier: %s", id)
385		}
386	}
387}
388