1
16
17 package versioning
18
19 import (
20 "fmt"
21 "io/ioutil"
22 "testing"
23
24 "github.com/stretchr/testify/assert"
25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 )
29
30 func buildUnstructuredDecodable(gvk schema.GroupVersionKind) runtime.Object {
31 obj := &unstructured.Unstructured{}
32 obj.SetGroupVersionKind(gvk)
33 return obj
34 }
35
36 func buildUnstructuredListDecodable(gvk schema.GroupVersionKind) runtime.Object {
37 obj := &unstructured.UnstructuredList{}
38 obj.SetGroupVersionKind(gvk)
39 return obj
40 }
41
42 func TestEncodeUnstructured(t *testing.T) {
43 v1GVK := schema.GroupVersionKind{
44 Group: "crispy",
45 Version: "v1",
46 Kind: "Noxu",
47 }
48 v2GVK := schema.GroupVersionKind{
49 Group: "crispy",
50 Version: "v2",
51 Kind: "Noxu",
52 }
53 elseGVK := schema.GroupVersionKind{
54 Group: "crispy2",
55 Version: "else",
56 Kind: "Noxu",
57 }
58 elseUnstructuredDecodable := buildUnstructuredDecodable(elseGVK)
59 elseUnstructuredDecodableList := buildUnstructuredListDecodable(elseGVK)
60 v1UnstructuredDecodable := buildUnstructuredDecodable(v1GVK)
61 v1UnstructuredDecodableList := buildUnstructuredListDecodable(v1GVK)
62 v2UnstructuredDecodable := buildUnstructuredDecodable(v2GVK)
63
64 testCases := []struct {
65 name string
66 convertor runtime.ObjectConvertor
67 targetVersion runtime.GroupVersioner
68 outObj runtime.Object
69 typer runtime.ObjectTyper
70
71 errFunc func(error) bool
72 expectedObj runtime.Object
73 }{
74 {
75 name: "encode v1 unstructured with v2 encode version",
76 typer: &mockTyper{
77 gvks: []schema.GroupVersionKind{v1GVK},
78 },
79 outObj: v1UnstructuredDecodable,
80 targetVersion: v2GVK.GroupVersion(),
81 convertor: &checkConvertor{
82 obj: v2UnstructuredDecodable,
83 groupVersion: v2GVK.GroupVersion(),
84 },
85 expectedObj: v2UnstructuredDecodable,
86 },
87 {
88 name: "both typer and conversion are bypassed when unstructured gvk matches encode gvk",
89 typer: &mockTyper{
90 err: fmt.Errorf("unexpected typer call"),
91 },
92 outObj: v1UnstructuredDecodable,
93 targetVersion: v1GVK.GroupVersion(),
94 convertor: &checkConvertor{
95 err: fmt.Errorf("unexpected conversion happened"),
96 },
97 expectedObj: v1UnstructuredDecodable,
98 },
99 {
100 name: "encode will fail when unstructured object's gvk and encode gvk mismatches",
101 outObj: elseUnstructuredDecodable,
102 targetVersion: v1GVK.GroupVersion(),
103 errFunc: func(err error) bool {
104 return assert.Equal(t, runtime.NewNotRegisteredGVKErrForTarget("noxu-scheme", elseGVK, v1GVK.GroupVersion()), err)
105 },
106 },
107 {
108 name: "encode with unstructured list's gvk regardless of its elements' gvk",
109 outObj: elseUnstructuredDecodableList,
110 targetVersion: elseGVK.GroupVersion(),
111 },
112 {
113 name: "typer fail to recognize unstructured object gvk will fail the encoding",
114 outObj: elseUnstructuredDecodable,
115 targetVersion: v1GVK.GroupVersion(),
116 typer: &mockTyper{
117 err: fmt.Errorf("invalid obj gvk"),
118 },
119 },
120 {
121 name: "encoding unstructured object without encode version will fallback to typer suggested version",
122 targetVersion: v1GVK.GroupVersion(),
123 convertor: &checkConvertor{
124 obj: v1UnstructuredDecodableList,
125 groupVersion: v1GVK.GroupVersion(),
126 },
127 outObj: elseUnstructuredDecodable,
128 typer: &mockTyper{
129 gvks: []schema.GroupVersionKind{v1GVK},
130 },
131 },
132 }
133 for _, testCase := range testCases {
134 serializer := &mockSerializer{}
135 codec := NewCodec(serializer, serializer, testCase.convertor, nil, testCase.typer, nil, testCase.targetVersion, nil, "noxu-scheme")
136 err := codec.Encode(testCase.outObj, ioutil.Discard)
137 if testCase.errFunc != nil {
138 if !testCase.errFunc(err) {
139 t.Errorf("%v: failed: %v", testCase.name, err)
140 }
141 return
142 }
143 assert.NoError(t, err)
144 assert.Equal(t, testCase.expectedObj, serializer.obj)
145 }
146 }
147
148 type errNotRecognizedGVK struct {
149 failedGVK schema.GroupVersionKind
150 claimingGVKs []schema.GroupVersionKind
151 }
152
153 func (e errNotRecognizedGVK) Error() string {
154 return fmt.Sprintf("unrecognized gvk %v, should be one of %v", e.failedGVK, e.claimingGVKs)
155 }
156
157 type mockUnstructuredNopConvertor struct {
158 claimingGVKs []schema.GroupVersionKind
159 }
160
161 func (c *mockUnstructuredNopConvertor) recognizeGVK(gvkToCheck schema.GroupVersionKind) error {
162 matched := false
163 for _, gvk := range c.claimingGVKs {
164 if gvk == gvkToCheck {
165 matched = true
166 }
167 }
168 if !matched {
169 return errNotRecognizedGVK{
170 failedGVK: gvkToCheck,
171 claimingGVKs: c.claimingGVKs,
172 }
173 }
174 return nil
175 }
176
177 func (c *mockUnstructuredNopConvertor) Convert(in, out, context interface{}) error {
178 inObj := in.(*unstructured.Unstructured)
179 outObj := out.(*unstructured.Unstructured)
180 if err := c.recognizeGVK(outObj.GroupVersionKind()); err != nil {
181 return err
182 }
183 outGVK := outObj.GetObjectKind().GroupVersionKind()
184 *outObj = *inObj.DeepCopy()
185 outObj.GetObjectKind().SetGroupVersionKind(outGVK)
186 return nil
187 }
188
189 func (c *mockUnstructuredNopConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (runtime.Object, error) {
190 out := in.DeepCopyObject()
191 targetGVK, matched := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{in.GetObjectKind().GroupVersionKind()})
192 if !matched {
193 return nil, fmt.Errorf("attempt to convert to mismatched gv %v", outVersion)
194 }
195 if err := c.recognizeGVK(out.GetObjectKind().GroupVersionKind()); err != nil {
196 return nil, err
197 }
198 out.GetObjectKind().SetGroupVersionKind(targetGVK)
199 return out, nil
200 }
201
202 func (c *mockUnstructuredNopConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
203 return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
204 }
205
206 func TestDecodeUnstructured(t *testing.T) {
207 internalGVK := schema.GroupVersionKind{
208 Group: "crispy",
209 Version: runtime.APIVersionInternal,
210 Kind: "Noxu",
211 }
212 v1GVK := schema.GroupVersionKind{
213 Group: "crispy",
214 Version: "v1",
215 Kind: "Noxu",
216 }
217 v2GVK := schema.GroupVersionKind{
218 Group: "crispy",
219 Version: "v2",
220 Kind: "Noxu",
221 }
222 internalUnstructuredDecodable := buildUnstructuredDecodable(internalGVK)
223 v1UnstructuredDecodable := buildUnstructuredDecodable(v1GVK)
224 v2UnstructuredDecodable := buildUnstructuredDecodable(v2GVK)
225
226 testCases := []struct {
227 name string
228 serializer runtime.Serializer
229 convertor runtime.ObjectConvertor
230 suggestedConvertVersion runtime.GroupVersioner
231 defaultGVK *schema.GroupVersionKind
232 intoObj runtime.Object
233
234 errFunc func(error) bool
235 expectedGVKOfSerializedData *schema.GroupVersionKind
236 expectedOut runtime.Object
237 }{
238 {
239 name: "decode v1 unstructured into non-nil v2 unstructured",
240 serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable},
241 convertor: &mockUnstructuredNopConvertor{
242 claimingGVKs: []schema.GroupVersionKind{
243 v1GVK, v2GVK,
244 },
245 },
246 suggestedConvertVersion: v2GVK.GroupVersion(),
247 intoObj: v2UnstructuredDecodable,
248 expectedGVKOfSerializedData: &v1GVK,
249 expectedOut: v2UnstructuredDecodable,
250 },
251 {
252 name: "decode v1 unstructured into nil object with v2 version",
253 serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable},
254 convertor: &mockUnstructuredNopConvertor{
255 claimingGVKs: []schema.GroupVersionKind{
256 v1GVK, v2GVK,
257 },
258 },
259 suggestedConvertVersion: v2GVK.GroupVersion(),
260 intoObj: nil,
261 expectedGVKOfSerializedData: &v1GVK,
262 expectedOut: v2UnstructuredDecodable,
263 },
264 {
265 name: "decode v1 unstructured into non-nil internal unstructured",
266 serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable},
267 convertor: &mockUnstructuredNopConvertor{
268 claimingGVKs: []schema.GroupVersionKind{
269 v1GVK, v2GVK,
270 },
271 },
272 suggestedConvertVersion: internalGVK.GroupVersion(),
273 intoObj: internalUnstructuredDecodable,
274 errFunc: func(err error) bool {
275 notRecognized, ok := err.(errNotRecognizedGVK)
276 if !ok {
277 return false
278 }
279 return assert.Equal(t, notRecognized.failedGVK, internalGVK)
280 },
281 },
282 {
283 name: "decode v1 unstructured into nil object with internal version",
284 serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable},
285 convertor: &mockUnstructuredNopConvertor{
286 claimingGVKs: []schema.GroupVersionKind{
287 v1GVK, v2GVK,
288 },
289 },
290 suggestedConvertVersion: internalGVK.GroupVersion(),
291 intoObj: nil,
292 errFunc: func(err error) bool {
293 notRecognized, ok := err.(errNotRecognizedGVK)
294 if !ok {
295 return false
296 }
297 return assert.Equal(t, notRecognized.failedGVK, internalGVK)
298 },
299 },
300 {
301 name: "skip conversion if serializer returns the same unstructured as into",
302 serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable},
303 convertor: &checkConvertor{
304 err: fmt.Errorf("unexpected conversion happened"),
305 },
306 suggestedConvertVersion: internalGVK.GroupVersion(),
307 intoObj: v1UnstructuredDecodable,
308 expectedGVKOfSerializedData: &v1GVK,
309 expectedOut: v1UnstructuredDecodable,
310 },
311 {
312 name: "invalid convert version makes decoding unstructured fail",
313 serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable},
314 convertor: &checkConvertor{
315 in: v1UnstructuredDecodable,
316 groupVersion: internalGVK.GroupVersion(),
317 err: fmt.Errorf("no matching decode version"),
318 },
319 suggestedConvertVersion: internalGVK.GroupVersion(),
320 errFunc: func(err error) bool {
321 return assert.Equal(t, err, fmt.Errorf("no matching decode version"))
322 },
323 },
324 }
325 for _, testCase := range testCases {
326 codec := NewCodec(testCase.serializer, testCase.serializer, testCase.convertor, nil, nil, nil, nil, testCase.suggestedConvertVersion, "noxu-scheme")
327 actualObj, actualSerializedGVK, err := codec.Decode([]byte(`{}`), testCase.defaultGVK, testCase.intoObj)
328 if testCase.errFunc != nil {
329 if !testCase.errFunc(err) {
330 t.Errorf("%v: failed: %v", testCase.name, err)
331 }
332 return
333 }
334 assert.NoError(t, err)
335 assert.Equal(t, testCase.expectedOut, actualObj, "%v failed", testCase.name)
336 assert.Equal(t, testCase.expectedGVKOfSerializedData, actualSerializedGVK, "%v failed", testCase.name)
337 }
338 }
339
View as plain text