k8s.io/apiserver@v0.31.1/pkg/admission/conversion_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package admission 18 19 import ( 20 "encoding/json" 21 "reflect" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/stretchr/testify/require" 26 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/apiserver/pkg/apis/example" 32 examplev1 "k8s.io/apiserver/pkg/apis/example/v1" 33 example2v1 "k8s.io/apiserver/pkg/apis/example2/v1" 34 ) 35 36 func initiateScheme(t *testing.T) *runtime.Scheme { 37 s := runtime.NewScheme() 38 require.NoError(t, example.AddToScheme(s)) 39 require.NoError(t, examplev1.AddToScheme(s)) 40 require.NoError(t, example2v1.AddToScheme(s)) 41 return s 42 } 43 44 func TestConvertToGVK(t *testing.T) { 45 scheme := initiateScheme(t) 46 o := NewObjectInterfacesFromScheme(scheme) 47 table := map[string]struct { 48 obj runtime.Object 49 gvk schema.GroupVersionKind 50 expectedObj runtime.Object 51 }{ 52 "convert example#Pod to example/v1#Pod": { 53 obj: &example.Pod{ 54 ObjectMeta: metav1.ObjectMeta{ 55 Name: "pod1", 56 Labels: map[string]string{ 57 "key": "value", 58 }, 59 }, 60 Spec: example.PodSpec{ 61 RestartPolicy: example.RestartPolicy("never"), 62 }, 63 }, 64 gvk: examplev1.SchemeGroupVersion.WithKind("Pod"), 65 expectedObj: &examplev1.Pod{ 66 TypeMeta: metav1.TypeMeta{ 67 APIVersion: "example.apiserver.k8s.io/v1", 68 Kind: "Pod", 69 }, 70 ObjectMeta: metav1.ObjectMeta{ 71 Name: "pod1", 72 Labels: map[string]string{ 73 "key": "value", 74 }, 75 }, 76 Spec: examplev1.PodSpec{ 77 RestartPolicy: examplev1.RestartPolicy("never"), 78 }, 79 }, 80 }, 81 "convert example#replicaset to example2/v1#replicaset": { 82 obj: &example.ReplicaSet{ 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: "rs1", 85 Labels: map[string]string{ 86 "key": "value", 87 }, 88 }, 89 Spec: example.ReplicaSetSpec{ 90 Replicas: 1, 91 }, 92 }, 93 gvk: example2v1.SchemeGroupVersion.WithKind("ReplicaSet"), 94 expectedObj: &example2v1.ReplicaSet{ 95 TypeMeta: metav1.TypeMeta{ 96 APIVersion: "example2.apiserver.k8s.io/v1", 97 Kind: "ReplicaSet", 98 }, 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: "rs1", 101 Labels: map[string]string{ 102 "key": "value", 103 }, 104 }, 105 Spec: example2v1.ReplicaSetSpec{ 106 Replicas: func() *int32 { var i int32 = 1; return &i }(), 107 }, 108 }, 109 }, 110 "no conversion for Unstructured object whose gvk matches the desired gvk": { 111 obj: &unstructured.Unstructured{ 112 Object: map[string]interface{}{ 113 "apiVersion": "mygroup.k8s.io/v1", 114 "kind": "Flunder", 115 "data": map[string]interface{}{ 116 "Key": "Value", 117 }, 118 }, 119 }, 120 gvk: schema.GroupVersionKind{Group: "mygroup.k8s.io", Version: "v1", Kind: "Flunder"}, 121 expectedObj: &unstructured.Unstructured{ 122 Object: map[string]interface{}{ 123 "apiVersion": "mygroup.k8s.io/v1", 124 "kind": "Flunder", 125 "data": map[string]interface{}{ 126 "Key": "Value", 127 }, 128 }, 129 }, 130 }, 131 } 132 133 for name, test := range table { 134 t.Run(name, func(t *testing.T) { 135 actual, err := ConvertToGVK(test.obj, test.gvk, o) 136 if err != nil { 137 t.Error(err) 138 } 139 if !reflect.DeepEqual(actual, test.expectedObj) { 140 t.Errorf("\nexpected:\n%#v\ngot:\n %#v\n", test.expectedObj, actual) 141 } 142 }) 143 } 144 } 145 146 // TestRuntimeSchemeConvert verifies that scheme.Convert(x, x, nil) for an unstructured x is a no-op. 147 // This did not use to be like that and we had to wrap scheme.Convert before. 148 func TestRuntimeSchemeConvert(t *testing.T) { 149 scheme := initiateScheme(t) 150 obj := &unstructured.Unstructured{ 151 Object: map[string]interface{}{ 152 "foo": "bar", 153 }, 154 } 155 clone := obj.DeepCopy() 156 157 if err := scheme.Convert(obj, obj, nil); err != nil { 158 t.Fatalf("unexpected convert error: %v", err) 159 } 160 if !reflect.DeepEqual(obj, clone) { 161 t.Errorf("unexpected mutation of self-converted Unstructured: obj=%#v, clone=%#v", obj, clone) 162 } 163 } 164 165 func TestConvertVersionedAttributes(t *testing.T) { 166 scheme := initiateScheme(t) 167 o := NewObjectInterfacesFromScheme(scheme) 168 169 gvk := func(g, v, k string) schema.GroupVersionKind { 170 return schema.GroupVersionKind{Group: g, Version: v, Kind: k} 171 } 172 attrs := func(obj, oldObj runtime.Object) Attributes { 173 return NewAttributesRecord(obj, oldObj, schema.GroupVersionKind{}, "", "", schema.GroupVersionResource{}, "", "", nil, false, nil) 174 } 175 u := func(data string) *unstructured.Unstructured { 176 t.Helper() 177 m := map[string]interface{}{} 178 if err := json.Unmarshal([]byte(data), &m); err != nil { 179 t.Fatal(err) 180 } 181 return &unstructured.Unstructured{Object: m} 182 } 183 testcases := []struct { 184 Name string 185 Attrs *VersionedAttributes 186 GVK schema.GroupVersionKind 187 ExpectedAttrs *VersionedAttributes 188 }{ 189 { 190 Name: "noop", 191 Attrs: &VersionedAttributes{ 192 Attributes: attrs( 193 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 194 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 195 ), 196 VersionedObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 197 VersionedOldObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpodversioned"}}, 198 VersionedKind: examplev1.SchemeGroupVersion.WithKind("Pod"), 199 Dirty: true, 200 }, 201 GVK: examplev1.SchemeGroupVersion.WithKind("Pod"), 202 ExpectedAttrs: &VersionedAttributes{ 203 Attributes: attrs( 204 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 205 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 206 ), 207 VersionedObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 208 VersionedOldObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpodversioned"}}, 209 VersionedKind: examplev1.SchemeGroupVersion.WithKind("Pod"), 210 Dirty: true, 211 }, 212 }, 213 { 214 Name: "clean, typed", 215 Attrs: &VersionedAttributes{ 216 Attributes: attrs( 217 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 218 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 219 ), 220 VersionedObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 221 VersionedOldObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpodversioned"}}, 222 VersionedKind: gvk("g", "v", "k"), 223 }, 224 GVK: examplev1.SchemeGroupVersion.WithKind("Pod"), 225 ExpectedAttrs: &VersionedAttributes{ 226 Attributes: attrs( 227 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 228 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 229 ), 230 // name gets overwritten from converted attributes, type gets set explicitly 231 VersionedObject: &examplev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "example.apiserver.k8s.io/v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 232 VersionedOldObject: &examplev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "example.apiserver.k8s.io/v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 233 VersionedKind: examplev1.SchemeGroupVersion.WithKind("Pod"), 234 }, 235 }, 236 { 237 Name: "clean, unstructured", 238 Attrs: &VersionedAttributes{ 239 Attributes: attrs( 240 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobj"}}`), 241 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobj"}}`), 242 ), 243 VersionedObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobjversioned"}}`), 244 VersionedOldObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobjversioned"}}`), 245 VersionedKind: gvk("g", "v", "k"), // claim a different current version to trigger conversion 246 }, 247 GVK: gvk("mygroup.k8s.io", "v1", "Flunder"), 248 ExpectedAttrs: &VersionedAttributes{ 249 Attributes: attrs( 250 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobj"}}`), 251 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobj"}}`), 252 ), 253 VersionedObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobj"}}`), 254 VersionedOldObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobj"}}`), 255 VersionedKind: gvk("mygroup.k8s.io", "v1", "Flunder"), 256 }, 257 }, 258 { 259 Name: "dirty, typed", 260 Attrs: &VersionedAttributes{ 261 Attributes: attrs( 262 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 263 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 264 ), 265 VersionedObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 266 VersionedOldObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpodversioned"}}, 267 VersionedKind: gvk("g", "v", "k"), // claim a different current version to trigger conversion 268 Dirty: true, 269 }, 270 GVK: examplev1.SchemeGroupVersion.WithKind("Pod"), 271 ExpectedAttrs: &VersionedAttributes{ 272 Attributes: attrs( 273 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 274 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 275 ), 276 // new name gets preserved from versioned object, type gets set explicitly 277 VersionedObject: &examplev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "example.apiserver.k8s.io/v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 278 // old name gets overwritten from converted attributes, type gets set explicitly 279 VersionedOldObject: &examplev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "example.apiserver.k8s.io/v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "oldpod"}}, 280 VersionedKind: examplev1.SchemeGroupVersion.WithKind("Pod"), 281 Dirty: false, 282 }, 283 }, 284 { 285 Name: "dirty, unstructured", 286 Attrs: &VersionedAttributes{ 287 Attributes: attrs( 288 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobj"}}`), 289 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobj"}}`), 290 ), 291 VersionedObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobjversioned"}}`), 292 VersionedOldObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobjversioned"}}`), 293 VersionedKind: gvk("g", "v", "k"), // claim a different current version to trigger conversion 294 Dirty: true, 295 }, 296 GVK: gvk("mygroup.k8s.io", "v1", "Flunder"), 297 ExpectedAttrs: &VersionedAttributes{ 298 Attributes: attrs( 299 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobjversioned"}}`), 300 u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobj"}}`), 301 ), 302 // new name gets preserved from versioned object, type gets set explicitly 303 VersionedObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"newobjversioned"}}`), 304 // old name gets overwritten from converted attributes, type gets set explicitly 305 VersionedOldObject: u(`{"apiVersion": "mygroup.k8s.io/v1","kind": "Flunder","metadata":{"name":"oldobj"}}`), 306 VersionedKind: gvk("mygroup.k8s.io", "v1", "Flunder"), 307 Dirty: false, 308 }, 309 }, 310 { 311 Name: "nil old object", 312 Attrs: &VersionedAttributes{ 313 Attributes: attrs( 314 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpod"}}, 315 nil, 316 ), 317 VersionedObject: &examplev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 318 VersionedOldObject: nil, 319 VersionedKind: gvk("g", "v", "k"), // claim a different current version to trigger conversion 320 Dirty: true, 321 }, 322 GVK: examplev1.SchemeGroupVersion.WithKind("Pod"), 323 ExpectedAttrs: &VersionedAttributes{ 324 Attributes: attrs( 325 &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 326 nil, 327 ), 328 // new name gets preserved from versioned object, type gets set explicitly 329 VersionedObject: &examplev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "example.apiserver.k8s.io/v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "newpodversioned"}}, 330 VersionedOldObject: nil, 331 VersionedKind: examplev1.SchemeGroupVersion.WithKind("Pod"), 332 Dirty: false, 333 }, 334 }, 335 } 336 337 for _, tc := range testcases { 338 t.Run(tc.Name, func(t *testing.T) { 339 err := ConvertVersionedAttributes(tc.Attrs, tc.GVK, o) 340 if err != nil { 341 t.Fatal(err) 342 } 343 if e, a := tc.ExpectedAttrs.Attributes.GetObject(), tc.Attrs.Attributes.GetObject(); !reflect.DeepEqual(e, a) { 344 t.Errorf("unexpected diff:\n%s", cmp.Diff(e, a)) 345 } 346 if e, a := tc.ExpectedAttrs.Attributes.GetOldObject(), tc.Attrs.Attributes.GetOldObject(); !reflect.DeepEqual(e, a) { 347 t.Errorf("unexpected diff:\n%s", cmp.Diff(e, a)) 348 } 349 if e, a := tc.ExpectedAttrs.VersionedKind, tc.Attrs.VersionedKind; !reflect.DeepEqual(e, a) { 350 t.Errorf("unexpected diff:\n%s", cmp.Diff(e, a)) 351 } 352 if e, a := tc.ExpectedAttrs.VersionedObject, tc.Attrs.VersionedObject; !reflect.DeepEqual(e, a) { 353 t.Errorf("unexpected diff:\n%s", cmp.Diff(e, a)) 354 } 355 if e, a := tc.ExpectedAttrs.VersionedOldObject, tc.Attrs.VersionedOldObject; !reflect.DeepEqual(e, a) { 356 t.Errorf("unexpected diff:\n%s", cmp.Diff(e, a)) 357 } 358 if e, a := tc.ExpectedAttrs.Dirty, tc.Attrs.Dirty; !reflect.DeepEqual(e, a) { 359 t.Errorf("unexpected diff:\n%s", cmp.Diff(e, a)) 360 } 361 }) 362 } 363 }