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