1/*
2Copyright 2014 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 runtime_test
18
19import (
20	"encoding/json"
21	"reflect"
22	"testing"
23
24	"k8s.io/apimachinery/pkg/api/meta"
25	"k8s.io/apimachinery/pkg/runtime"
26	"k8s.io/apimachinery/pkg/runtime/schema"
27	"k8s.io/apimachinery/pkg/runtime/serializer"
28	runtimetesting "k8s.io/apimachinery/pkg/runtime/testing"
29	"k8s.io/apimachinery/pkg/util/diff"
30	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
31)
32
33func TestDecodeEmptyRawExtensionAsObject(t *testing.T) {
34	internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
35	externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"}
36	externalGVK := externalGV.WithKind("ObjectTest")
37
38	s := runtime.NewScheme()
39	s.AddKnownTypes(internalGV, &runtimetesting.ObjectTest{})
40	s.AddKnownTypeWithName(externalGVK, &runtimetesting.ObjectTestExternal{})
41	utilruntime.Must(runtimetesting.RegisterConversions(s))
42
43	codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
44
45	obj, gvk, err := codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{}]}`), nil, nil)
46	if err != nil {
47		t.Fatalf("unexpected error: %v", err)
48	}
49	test := obj.(*runtimetesting.ObjectTest)
50	if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != "{}" || unk.ContentType != runtime.ContentTypeJSON {
51		t.Fatalf("unexpected object: %#v", test.Items[0])
52	}
53	if *gvk != externalGVK {
54		t.Fatalf("unexpected kind: %#v", gvk)
55	}
56
57	obj, gvk, err = codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{"kind":"Other","apiVersion":"v1"}]}`), nil, nil)
58	if err != nil {
59		t.Fatalf("unexpected error: %v", err)
60	}
61	test = obj.(*runtimetesting.ObjectTest)
62	if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != `{"kind":"Other","apiVersion":"v1"}` || unk.ContentType != runtime.ContentTypeJSON {
63		t.Fatalf("unexpected object: %#v", test.Items[0])
64	}
65	if *gvk != externalGVK {
66		t.Fatalf("unexpected kind: %#v", gvk)
67	}
68}
69
70func TestArrayOfRuntimeObject(t *testing.T) {
71	internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
72	externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"}
73
74	s := runtime.NewScheme()
75	s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{})
76	s.AddKnownTypeWithName(externalGV.WithKind("EmbeddedTest"), &runtimetesting.EmbeddedTestExternal{})
77	s.AddKnownTypes(internalGV, &runtimetesting.ObjectTest{})
78	s.AddKnownTypeWithName(externalGV.WithKind("ObjectTest"), &runtimetesting.ObjectTestExternal{})
79	utilruntime.Must(runtimetesting.RegisterConversions(s))
80
81	codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
82
83	innerItems := []runtime.Object{
84		&runtimetesting.EmbeddedTest{ID: "baz"},
85	}
86	items := []runtime.Object{
87		&runtimetesting.EmbeddedTest{ID: "foo"},
88		&runtimetesting.EmbeddedTest{ID: "bar"},
89		// TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization
90		&runtime.Unknown{
91			Raw:         []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`),
92			ContentType: runtime.ContentTypeJSON,
93		},
94		&runtimetesting.ObjectTest{
95			Items: runtime.NewEncodableList(codec, innerItems),
96		},
97	}
98	internal := &runtimetesting.ObjectTest{
99		Items: runtime.NewEncodableList(codec, items),
100	}
101	wire, err := runtime.Encode(codec, internal)
102	if err != nil {
103		t.Fatalf("unexpected error: %v", err)
104	}
105	t.Logf("Wire format is:\n%s\n", string(wire))
106
107	obj := &runtimetesting.ObjectTestExternal{}
108	if err := json.Unmarshal(wire, obj); err != nil {
109		t.Fatalf("unexpected error: %v", err)
110	}
111	t.Logf("exact wire is: %s", string(obj.Items[0].Raw))
112
113	items[3] = &runtimetesting.ObjectTest{Items: innerItems}
114	internal.Items = items
115
116	decoded, err := runtime.Decode(codec, wire)
117	if err != nil {
118		t.Fatalf("unexpected error: %v", err)
119	}
120	list, err := meta.ExtractList(decoded)
121	if err != nil {
122		t.Fatalf("unexpected error: %v", err)
123	}
124	if errs := runtime.DecodeList(list, codec); len(errs) > 0 {
125		t.Fatalf("unexpected error: %v", errs)
126	}
127
128	list2, err := meta.ExtractList(list[3])
129	if err != nil {
130		t.Fatalf("unexpected error: %v", err)
131	}
132	if errs := runtime.DecodeList(list2, codec); len(errs) > 0 {
133		t.Fatalf("unexpected error: %v", errs)
134	}
135	if err := meta.SetList(list[3], list2); err != nil {
136		t.Fatalf("unexpected error: %v", err)
137	}
138
139	// we want DecodeList to set type meta if possible, even on runtime.Unknown objects
140	internal.Items[2].(*runtime.Unknown).TypeMeta = runtime.TypeMeta{Kind: "OtherTest", APIVersion: "unknown.group/unknown"}
141	if e, a := internal.Items, list; !reflect.DeepEqual(e, a) {
142		t.Errorf("mismatched decoded: %s", diff.ObjectGoPrintSideBySide(e, a))
143	}
144}
145
146func TestNestedObject(t *testing.T) {
147	internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
148	externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"}
149	embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest")
150
151	s := runtime.NewScheme()
152	s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{})
153	s.AddKnownTypeWithName(embeddedTestExternalGVK, &runtimetesting.EmbeddedTestExternal{})
154	utilruntime.Must(runtimetesting.RegisterConversions(s))
155
156	codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
157
158	inner := &runtimetesting.EmbeddedTest{
159		ID: "inner",
160	}
161	outer := &runtimetesting.EmbeddedTest{
162		ID:     "outer",
163		Object: runtime.NewEncodable(codec, inner),
164	}
165
166	wire, err := runtime.Encode(codec, outer)
167	if err != nil {
168		t.Fatalf("Unexpected encode error '%v'", err)
169	}
170
171	t.Logf("Wire format is:\n%v\n", string(wire))
172
173	decoded, err := runtime.Decode(codec, wire)
174	if err != nil {
175		t.Fatalf("Unexpected decode error %v", err)
176	}
177
178	// for later tests
179	outer.Object = inner
180
181	if e, a := outer, decoded; reflect.DeepEqual(e, a) {
182		t.Errorf("Expected unequal %#v %#v", e, a)
183	}
184
185	obj, err := runtime.Decode(codec, decoded.(*runtimetesting.EmbeddedTest).Object.(*runtime.Unknown).Raw)
186	if err != nil {
187		t.Fatal(err)
188	}
189	decoded.(*runtimetesting.EmbeddedTest).Object = obj
190	if e, a := outer, decoded; !reflect.DeepEqual(e, a) {
191		t.Errorf("Expected equal %#v %#v", e, a)
192	}
193
194	// test JSON decoding of the external object, which should preserve
195	// raw bytes
196	var externalViaJSON runtimetesting.EmbeddedTestExternal
197	err = json.Unmarshal(wire, &externalViaJSON)
198	if err != nil {
199		t.Fatalf("Unexpected decode error %v", err)
200	}
201	if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" {
202		t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON)
203	}
204	if len(externalViaJSON.EmptyObject.Raw) > 0 {
205		t.Errorf("Expected deserialization of empty nested objects into empty bytes, got %#v", externalViaJSON)
206	}
207
208	// test JSON decoding, too, since Decode uses yaml unmarshalling.
209	// Generic Unmarshalling of JSON cannot load the nested objects because there is
210	// no default schema set.  Consumers wishing to get direct JSON decoding must use
211	// the external representation
212	var decodedViaJSON runtimetesting.EmbeddedTest
213	err = json.Unmarshal(wire, &decodedViaJSON)
214	if err == nil {
215		t.Fatal("Expeceted decode error")
216	}
217	if _, ok := err.(*json.UnmarshalTypeError); !ok {
218		t.Fatalf("Unexpected decode error: %v", err)
219	}
220	if a := decodedViaJSON; a.Object != nil || a.EmptyObject != nil {
221		t.Errorf("Expected embedded objects to be nil: %#v", a)
222	}
223}
224
225// TestDeepCopyOfRuntimeObject checks to make sure that runtime.Objects's can be passed through DeepCopy with fidelity
226func TestDeepCopyOfRuntimeObject(t *testing.T) {
227	internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
228	externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"}
229	embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest")
230
231	s := runtime.NewScheme()
232	s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{})
233	s.AddKnownTypeWithName(embeddedTestExternalGVK, &runtimetesting.EmbeddedTestExternal{})
234	utilruntime.Must(runtimetesting.RegisterConversions(s))
235
236	original := &runtimetesting.EmbeddedTest{
237		ID: "outer",
238		Object: &runtimetesting.EmbeddedTest{
239			ID: "inner",
240		},
241	}
242
243	codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
244
245	originalData, err := runtime.Encode(codec, original)
246	if err != nil {
247		t.Errorf("unexpected error: %v", err)
248	}
249	t.Logf("originalRole = %v\n", string(originalData))
250
251	copyOfOriginal := original.DeepCopy()
252	copiedData, err := runtime.Encode(codec, copyOfOriginal)
253	if err != nil {
254		t.Errorf("unexpected error: %v", err)
255	}
256	t.Logf("copyOfRole   = %v\n", string(copiedData))
257
258	if !reflect.DeepEqual(original, copyOfOriginal) {
259		t.Errorf("expected \n%v\n, got \n%v", string(originalData), string(copiedData))
260	}
261}
262