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  }