...

Source file src/k8s.io/client-go/restmapper/shortcut_test.go

Documentation: k8s.io/client-go/restmapper

     1  /*
     2  Copyright 2016 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 restmapper
    18  
    19  import (
    20  	"testing"
    21  
    22  	openapi_v2 "github.com/google/gnostic-models/openapiv2"
    23  	"github.com/google/go-cmp/cmp"
    24  
    25  	"k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/version"
    30  	"k8s.io/client-go/discovery"
    31  	"k8s.io/client-go/openapi"
    32  	restclient "k8s.io/client-go/rest"
    33  	"k8s.io/client-go/rest/fake"
    34  )
    35  
    36  func TestReplaceAliases(t *testing.T) {
    37  	tests := []struct {
    38  		name     string
    39  		arg      string
    40  		expected schema.GroupVersionResource
    41  		srvRes   []*metav1.APIResourceList
    42  	}{
    43  		{
    44  			name:     "storageclasses-no-replacement",
    45  			arg:      "storageclasses",
    46  			expected: schema.GroupVersionResource{Resource: "storageclasses"},
    47  			srvRes:   []*metav1.APIResourceList{},
    48  		},
    49  		{
    50  			name:     "hpa-priority",
    51  			arg:      "hpa",
    52  			expected: schema.GroupVersionResource{Resource: "superhorizontalpodautoscalers", Group: "autoscaling"},
    53  			srvRes: []*metav1.APIResourceList{
    54  				{
    55  					GroupVersion: "autoscaling/v1",
    56  					APIResources: []metav1.APIResource{
    57  						{
    58  							Name:       "superhorizontalpodautoscalers",
    59  							ShortNames: []string{"hpa"},
    60  						},
    61  					},
    62  				},
    63  				{
    64  					GroupVersion: "autoscaling/v1",
    65  					APIResources: []metav1.APIResource{
    66  						{
    67  							Name:       "horizontalpodautoscalers",
    68  							ShortNames: []string{"hpa"},
    69  						},
    70  					},
    71  				},
    72  			},
    73  		},
    74  		{
    75  			name:     "resource-override",
    76  			arg:      "dpl",
    77  			expected: schema.GroupVersionResource{Resource: "deployments", Group: "foo"},
    78  			srvRes: []*metav1.APIResourceList{
    79  				{
    80  					GroupVersion: "foo/v1",
    81  					APIResources: []metav1.APIResource{
    82  						{
    83  							Name:       "deployments",
    84  							ShortNames: []string{"dpl"},
    85  						},
    86  					},
    87  				},
    88  				{
    89  					GroupVersion: "extension/v1beta1",
    90  					APIResources: []metav1.APIResource{
    91  						{
    92  							Name:       "deployments",
    93  							ShortNames: []string{"deploy"},
    94  						},
    95  					},
    96  				},
    97  			},
    98  		},
    99  		{
   100  			name:     "resource-match-preferred",
   101  			arg:      "pods",
   102  			expected: schema.GroupVersionResource{Resource: "pods", Group: ""},
   103  			srvRes: []*metav1.APIResourceList{
   104  				{
   105  					GroupVersion: "v1",
   106  					APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod"}},
   107  				},
   108  				{
   109  					GroupVersion: "acme.com/v1",
   110  					APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"pods", "pod"}}},
   111  				},
   112  			},
   113  		},
   114  		{
   115  			name:     "resource-match-singular-preferred",
   116  			arg:      "pod",
   117  			expected: schema.GroupVersionResource{Resource: "pod", Group: ""},
   118  			srvRes: []*metav1.APIResourceList{
   119  				{
   120  					GroupVersion: "v1",
   121  					APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod"}},
   122  				},
   123  				{
   124  					GroupVersion: "acme.com/v1",
   125  					APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"pods", "pod"}}},
   126  				},
   127  			},
   128  		},
   129  	}
   130  
   131  	for _, test := range tests {
   132  		ds := &fakeDiscoveryClient{}
   133  		ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
   134  			return test.srvRes, nil
   135  		}
   136  		mapper := NewShortcutExpander(&fakeRESTMapper{}, ds, nil).(shortcutExpander)
   137  
   138  		actual := mapper.expandResourceShortcut(schema.GroupVersionResource{Resource: test.arg})
   139  		if actual != test.expected {
   140  			t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, actual)
   141  		}
   142  	}
   143  }
   144  
   145  func TestKindFor(t *testing.T) {
   146  	tests := []struct {
   147  		in       schema.GroupVersionResource
   148  		expected schema.GroupVersionResource
   149  		srvRes   []*metav1.APIResourceList
   150  	}{
   151  		{
   152  			in:       schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "sc"},
   153  			expected: schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "storageclasses"},
   154  			srvRes: []*metav1.APIResourceList{
   155  				{
   156  					GroupVersion: "storage.k8s.io/v1",
   157  					APIResources: []metav1.APIResource{
   158  						{
   159  							Name:       "storageclasses",
   160  							ShortNames: []string{"sc"},
   161  						},
   162  					},
   163  				},
   164  			},
   165  		},
   166  		{
   167  			in:       schema.GroupVersionResource{Group: "", Version: "", Resource: "sc"},
   168  			expected: schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "storageclasses"},
   169  			srvRes: []*metav1.APIResourceList{
   170  				{
   171  					GroupVersion: "storage.k8s.io/v1",
   172  					APIResources: []metav1.APIResource{
   173  						{
   174  							Name:       "storageclasses",
   175  							ShortNames: []string{"sc"},
   176  						},
   177  					},
   178  				},
   179  			},
   180  		},
   181  	}
   182  
   183  	for i, test := range tests {
   184  		ds := &fakeDiscoveryClient{}
   185  		ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
   186  			return test.srvRes, nil
   187  		}
   188  
   189  		delegate := &fakeRESTMapper{}
   190  		mapper := NewShortcutExpander(delegate, ds, func(a string) {
   191  			t.Fatalf("unexpected warning message %s", a)
   192  		})
   193  
   194  		mapper.KindFor(test.in)
   195  		if delegate.kindForInput != test.expected {
   196  			t.Errorf("%d: unexpected data returned %#v, expected %#v", i, delegate.kindForInput, test.expected)
   197  		}
   198  	}
   199  }
   200  
   201  func TestKindForWithNewCRDs(t *testing.T) {
   202  	tests := map[string]struct {
   203  		in       schema.GroupVersionResource
   204  		expected schema.GroupVersionKind
   205  		srvRes   []*metav1.APIResourceList
   206  	}{
   207  		"": {
   208  			in:       schema.GroupVersionResource{Group: "a", Version: "", Resource: "sc"},
   209  			expected: schema.GroupVersionKind{Group: "a", Version: "v1", Kind: "StorageClass"},
   210  			srvRes: []*metav1.APIResourceList{
   211  				{
   212  					GroupVersion: "a/v1",
   213  					APIResources: []metav1.APIResource{
   214  						{
   215  							Name:       "storageclasses",
   216  							ShortNames: []string{"sc"},
   217  							Kind:       "StorageClass",
   218  						},
   219  					},
   220  				},
   221  			},
   222  		},
   223  	}
   224  
   225  	for name, test := range tests {
   226  		t.Run(name, func(t *testing.T) {
   227  			invalidateCalled := false
   228  			fakeDiscovery := &fakeDiscoveryClient{}
   229  			fakeDiscovery.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
   230  				if invalidateCalled {
   231  					return test.srvRes, nil
   232  				}
   233  				return []*metav1.APIResourceList{}, nil
   234  			}
   235  			fakeCachedDiscovery := &fakeCachedDiscoveryClient{DiscoveryInterface: fakeDiscovery}
   236  			fakeCachedDiscovery.invalidateHandler = func() {
   237  				invalidateCalled = true
   238  			}
   239  			fakeCachedDiscovery.freshHandler = func() bool {
   240  				return invalidateCalled
   241  			}
   242  
   243  			// in real world the discovery client is fronted with a cache which
   244  			// will answer the initial request, only failure to match will trigger
   245  			// the cache invalidation and live discovery call
   246  			delegate := NewDeferredDiscoveryRESTMapper(fakeCachedDiscovery)
   247  			mapper := NewShortcutExpander(delegate, fakeCachedDiscovery, func(a string) {
   248  				t.Fatalf("unexpected warning message %s", a)
   249  			})
   250  
   251  			gvk, err := mapper.KindFor(test.in)
   252  			if err != nil {
   253  				t.Errorf("unexpected error: %v", err)
   254  			}
   255  			if diff := cmp.Equal(gvk, test.expected); !diff {
   256  				t.Errorf("unexpected data returned %#v, expected %#v", gvk, test.expected)
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func TestWarnAmbigious(t *testing.T) {
   263  	tests := []struct {
   264  		name                string
   265  		arg                 string
   266  		expected            schema.GroupVersionResource
   267  		expectedWarningLogs []string
   268  		srvRes              []*metav1.APIResourceList
   269  	}{
   270  		{
   271  			name:                "warn ambiguity",
   272  			arg:                 "hpa",
   273  			expected:            schema.GroupVersionResource{Resource: "superhorizontalpodautoscalers", Group: "autoscaling"},
   274  			expectedWarningLogs: []string{`short name "hpa" could also match lower priority resource horizontalpodautoscalers.autoscaling`},
   275  			srvRes: []*metav1.APIResourceList{
   276  				{
   277  					GroupVersion: "autoscaling/v1",
   278  					APIResources: []metav1.APIResource{
   279  						{
   280  							Name:       "superhorizontalpodautoscalers",
   281  							ShortNames: []string{"hpa"},
   282  						},
   283  					},
   284  				},
   285  				{
   286  					GroupVersion: "autoscaling/v1",
   287  					APIResources: []metav1.APIResource{
   288  						{
   289  							Name:       "horizontalpodautoscalers",
   290  							ShortNames: []string{"hpa"},
   291  						},
   292  					},
   293  				},
   294  			},
   295  		},
   296  		{
   297  			name:                "warn-builtin-shortname-ambugity",
   298  			arg:                 "po",
   299  			expected:            schema.GroupVersionResource{Resource: "pods", Group: ""},
   300  			expectedWarningLogs: []string{`short name "po" could also match lower priority resource poddlers.acme.com`},
   301  			srvRes: []*metav1.APIResourceList{
   302  				{
   303  					GroupVersion: "v1",
   304  					APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod", ShortNames: []string{"po"}}},
   305  				},
   306  				{
   307  					GroupVersion: "acme.com/v1",
   308  					APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"po"}}},
   309  				},
   310  			},
   311  		},
   312  		{
   313  			name:                "warn-builtin-shortname-ambugity-multi-version",
   314  			arg:                 "po",
   315  			expected:            schema.GroupVersionResource{Resource: "pods", Group: ""},
   316  			expectedWarningLogs: []string{`short name "po" could also match lower priority resource poddlers.acme.com`},
   317  			srvRes: []*metav1.APIResourceList{
   318  				{
   319  					GroupVersion: "v1",
   320  					APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod", ShortNames: []string{"po"}}},
   321  				},
   322  				{
   323  					GroupVersion: "acme.com/v1",
   324  					APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"po"}}},
   325  				},
   326  				{
   327  					GroupVersion: "acme.com/v1beta1",
   328  					APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"po"}}},
   329  				},
   330  			},
   331  		},
   332  		{
   333  			name:     "resource-match-singular-preferred",
   334  			arg:      "pod",
   335  			expected: schema.GroupVersionResource{Resource: "pod", Group: ""},
   336  			srvRes: []*metav1.APIResourceList{
   337  				{
   338  					GroupVersion: "v1",
   339  					APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod"}},
   340  				},
   341  				{
   342  					GroupVersion: "acme.com/v1",
   343  					APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"pods", "pod"}}},
   344  				},
   345  			},
   346  		},
   347  		{
   348  			name:                "resource-multiple-versions-shortform",
   349  			arg:                 "hpa",
   350  			expected:            schema.GroupVersionResource{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
   351  			expectedWarningLogs: []string{},
   352  			srvRes: []*metav1.APIResourceList{
   353  				{
   354  					GroupVersion: "autoscaling/v1alphav1",
   355  					APIResources: []metav1.APIResource{
   356  						{
   357  							Name:       "horizontalpodautoscalers",
   358  							ShortNames: []string{"hpa"},
   359  						},
   360  					},
   361  				},
   362  				{
   363  					GroupVersion: "autoscaling/v1",
   364  					APIResources: []metav1.APIResource{
   365  						{
   366  							Name:       "horizontalpodautoscalers",
   367  							ShortNames: []string{"hpa"},
   368  						},
   369  					},
   370  				},
   371  			},
   372  		},
   373  		{
   374  			name:     "multi-resource-multiple-versions-shortform",
   375  			arg:      "hpa",
   376  			expected: schema.GroupVersionResource{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
   377  			expectedWarningLogs: []string{
   378  				`short name "hpa" could also match lower priority resource foo.foo`,
   379  				`short name "hpa" could also match lower priority resource bar.bar`,
   380  			},
   381  			srvRes: []*metav1.APIResourceList{
   382  				{
   383  					GroupVersion: "autoscaling/v1alphav1",
   384  					APIResources: []metav1.APIResource{
   385  						{
   386  							Name:       "horizontalpodautoscalers",
   387  							ShortNames: []string{"hpa"},
   388  						},
   389  					},
   390  				},
   391  				{
   392  					GroupVersion: "autoscaling/v1",
   393  					APIResources: []metav1.APIResource{
   394  						{
   395  							Name:       "horizontalpodautoscalers",
   396  							ShortNames: []string{"hpa"},
   397  						},
   398  					},
   399  				},
   400  				{
   401  					GroupVersion: "foo/v1",
   402  					APIResources: []metav1.APIResource{
   403  						{
   404  							Name:       "foo",
   405  							ShortNames: []string{"hpa"},
   406  						},
   407  					},
   408  				},
   409  				{
   410  					GroupVersion: "foo/v1beta1",
   411  					APIResources: []metav1.APIResource{
   412  						{
   413  							Name:       "foo",
   414  							ShortNames: []string{"hpa"},
   415  						},
   416  					},
   417  				},
   418  				{
   419  					GroupVersion: "bar/v1",
   420  					APIResources: []metav1.APIResource{
   421  						{
   422  							Name:       "bar",
   423  							ShortNames: []string{"hpa"},
   424  						},
   425  					},
   426  				},
   427  			},
   428  		},
   429  	}
   430  
   431  	for _, test := range tests {
   432  		ds := &fakeDiscoveryClient{}
   433  		ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
   434  			return test.srvRes, nil
   435  		}
   436  
   437  		var actualWarnings []string
   438  		mapper := NewShortcutExpander(&fakeRESTMapper{}, ds, func(a string) {
   439  			actualWarnings = append(actualWarnings, a)
   440  		}).(shortcutExpander)
   441  
   442  		actual := mapper.expandResourceShortcut(schema.GroupVersionResource{Resource: test.arg})
   443  		if actual != test.expected {
   444  			t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, actual)
   445  		}
   446  
   447  		if len(actualWarnings) == 0 && len(test.expectedWarningLogs) == 0 {
   448  			continue
   449  		}
   450  
   451  		if !cmp.Equal(test.expectedWarningLogs, actualWarnings) {
   452  			t.Fatalf("expected warning message %s but got %s", test.expectedWarningLogs, actualWarnings)
   453  		}
   454  	}
   455  }
   456  
   457  type fakeRESTMapper struct {
   458  	kindForInput schema.GroupVersionResource
   459  }
   460  
   461  func (f *fakeRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
   462  	f.kindForInput = resource
   463  	return schema.GroupVersionKind{}, nil
   464  }
   465  
   466  func (f *fakeRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
   467  	return nil, nil
   468  }
   469  
   470  func (f *fakeRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
   471  	return schema.GroupVersionResource{}, nil
   472  }
   473  
   474  func (f *fakeRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
   475  	return nil, nil
   476  }
   477  
   478  func (f *fakeRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
   479  	return nil, nil
   480  }
   481  
   482  func (f *fakeRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
   483  	return nil, nil
   484  }
   485  
   486  func (f *fakeRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
   487  	return "", nil
   488  }
   489  
   490  type fakeDiscoveryClient struct {
   491  	serverResourcesHandler func() ([]*metav1.APIResourceList, error)
   492  }
   493  
   494  var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
   495  
   496  func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
   497  	return &fake.RESTClient{}
   498  }
   499  
   500  func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
   501  	return &metav1.APIGroupList{
   502  		Groups: []metav1.APIGroup{
   503  			{
   504  				Name: "a",
   505  				Versions: []metav1.GroupVersionForDiscovery{
   506  					{
   507  						GroupVersion: "a/v1",
   508  						Version:      "v1",
   509  					},
   510  				},
   511  				PreferredVersion: metav1.GroupVersionForDiscovery{
   512  					GroupVersion: "a/v1",
   513  					Version:      "v1",
   514  				},
   515  			},
   516  		},
   517  	}, nil
   518  }
   519  
   520  func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
   521  	if groupVersion == "a/v1" {
   522  		return &metav1.APIResourceList{APIResources: []metav1.APIResource{{Name: "widgets", Kind: "Widget"}}}, nil
   523  	}
   524  
   525  	return nil, errors.NewNotFound(schema.GroupResource{}, "")
   526  }
   527  
   528  func (c *fakeDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
   529  	sgs, err := c.ServerGroups()
   530  	if err != nil {
   531  		return nil, nil, err
   532  	}
   533  	resultGroups := []*metav1.APIGroup{}
   534  	for i := range sgs.Groups {
   535  		resultGroups = append(resultGroups, &sgs.Groups[i])
   536  	}
   537  	if c.serverResourcesHandler != nil {
   538  		rs, err := c.serverResourcesHandler()
   539  		return resultGroups, rs, err
   540  	}
   541  	return resultGroups, []*metav1.APIResourceList{}, nil
   542  }
   543  
   544  func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
   545  	return nil, nil
   546  }
   547  
   548  func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
   549  	return nil, nil
   550  }
   551  
   552  func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
   553  	return &version.Info{}, nil
   554  }
   555  
   556  func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
   557  	return &openapi_v2.Document{}, nil
   558  }
   559  
   560  func (c *fakeDiscoveryClient) OpenAPIV3() openapi.Client {
   561  	panic("implement me")
   562  }
   563  
   564  func (c *fakeDiscoveryClient) WithLegacy() discovery.DiscoveryInterface {
   565  	panic("implement me")
   566  }
   567  
   568  type fakeCachedDiscoveryClient struct {
   569  	discovery.DiscoveryInterface
   570  	freshHandler      func() bool
   571  	invalidateHandler func()
   572  }
   573  
   574  var _ discovery.CachedDiscoveryInterface = &fakeCachedDiscoveryClient{}
   575  
   576  func (c *fakeCachedDiscoveryClient) Fresh() bool {
   577  	if c.freshHandler != nil {
   578  		return c.freshHandler()
   579  	}
   580  	return true
   581  }
   582  
   583  func (c *fakeCachedDiscoveryClient) Invalidate() {
   584  	if c.invalidateHandler != nil {
   585  		c.invalidateHandler()
   586  	}
   587  }
   588  

View as plain text