...

Source file src/k8s.io/client-go/restmapper/discovery_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  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"k8s.io/apimachinery/pkg/util/dump"
    28  	"k8s.io/apimachinery/pkg/version"
    29  	. "k8s.io/client-go/discovery"
    30  	"k8s.io/client-go/openapi"
    31  	restclient "k8s.io/client-go/rest"
    32  	"k8s.io/client-go/rest/fake"
    33  
    34  	openapi_v2 "github.com/google/gnostic-models/openapiv2"
    35  	"github.com/stretchr/testify/assert"
    36  )
    37  
    38  func TestRESTMapper(t *testing.T) {
    39  	resources := []*APIGroupResources{
    40  		{
    41  			Group: metav1.APIGroup{
    42  				Name: "extensions",
    43  				Versions: []metav1.GroupVersionForDiscovery{
    44  					{Version: "v1beta"},
    45  				},
    46  				PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1beta"},
    47  			},
    48  			VersionedResources: map[string][]metav1.APIResource{
    49  				"v1beta": {
    50  					{Name: "jobs", Namespaced: true, Kind: "Job"},
    51  					{Name: "pods", Namespaced: true, Kind: "Pod"},
    52  				},
    53  			},
    54  		},
    55  		{
    56  			Group: metav1.APIGroup{
    57  				Versions: []metav1.GroupVersionForDiscovery{
    58  					{Version: "v1"},
    59  					{Version: "v2"},
    60  				},
    61  				PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
    62  			},
    63  			VersionedResources: map[string][]metav1.APIResource{
    64  				"v1": {
    65  					{Name: "pods", Namespaced: true, Kind: "Pod"},
    66  				},
    67  				"v2": {
    68  					{Name: "pods", Namespaced: true, Kind: "Pod"},
    69  				},
    70  			},
    71  		},
    72  
    73  		// This group tests finding and prioritizing resources that only exist in non-preferred versions
    74  		{
    75  			Group: metav1.APIGroup{
    76  				Name: "unpreferred",
    77  				Versions: []metav1.GroupVersionForDiscovery{
    78  					{Version: "v1"},
    79  					{Version: "v2beta1"},
    80  					{Version: "v2alpha1"},
    81  				},
    82  				PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
    83  			},
    84  			VersionedResources: map[string][]metav1.APIResource{
    85  				"v1": {
    86  					{Name: "broccoli", Namespaced: true, Kind: "Broccoli"},
    87  				},
    88  				"v2beta1": {
    89  					{Name: "broccoli", Namespaced: true, Kind: "Broccoli"},
    90  					{Name: "peas", Namespaced: true, Kind: "Pea"},
    91  				},
    92  				"v2alpha1": {
    93  					{Name: "broccoli", Namespaced: true, Kind: "Broccoli"},
    94  					{Name: "peas", Namespaced: true, Kind: "Pea"},
    95  				},
    96  			},
    97  		},
    98  	}
    99  
   100  	restMapper := NewDiscoveryRESTMapper(resources)
   101  
   102  	kindTCs := []struct {
   103  		input schema.GroupVersionResource
   104  		want  schema.GroupVersionKind
   105  	}{
   106  		{
   107  			input: schema.GroupVersionResource{
   108  				Resource: "pods",
   109  			},
   110  			want: schema.GroupVersionKind{
   111  				Version: "v1",
   112  				Kind:    "Pod",
   113  			},
   114  		},
   115  		{
   116  			input: schema.GroupVersionResource{
   117  				Version:  "v1",
   118  				Resource: "pods",
   119  			},
   120  			want: schema.GroupVersionKind{
   121  				Version: "v1",
   122  				Kind:    "Pod",
   123  			},
   124  		},
   125  		{
   126  			input: schema.GroupVersionResource{
   127  				Version:  "v2",
   128  				Resource: "pods",
   129  			},
   130  			want: schema.GroupVersionKind{
   131  				Version: "v2",
   132  				Kind:    "Pod",
   133  			},
   134  		},
   135  		{
   136  			input: schema.GroupVersionResource{
   137  				Resource: "pods",
   138  			},
   139  			want: schema.GroupVersionKind{
   140  				Version: "v1",
   141  				Kind:    "Pod",
   142  			},
   143  		},
   144  		{
   145  			input: schema.GroupVersionResource{
   146  				Resource: "jobs",
   147  			},
   148  			want: schema.GroupVersionKind{
   149  				Group:   "extensions",
   150  				Version: "v1beta",
   151  				Kind:    "Job",
   152  			},
   153  		},
   154  		{
   155  			input: schema.GroupVersionResource{
   156  				Resource: "peas",
   157  			},
   158  			want: schema.GroupVersionKind{
   159  				Group:   "unpreferred",
   160  				Version: "v2beta1",
   161  				Kind:    "Pea",
   162  			},
   163  		},
   164  	}
   165  
   166  	for _, tc := range kindTCs {
   167  		got, err := restMapper.KindFor(tc.input)
   168  		if err != nil {
   169  			t.Errorf("KindFor(%#v) unexpected error: %v", tc.input, err)
   170  			continue
   171  		}
   172  
   173  		if !reflect.DeepEqual(got, tc.want) {
   174  			t.Errorf("KindFor(%#v) = %#v, want %#v", tc.input, got, tc.want)
   175  		}
   176  	}
   177  
   178  	resourceTCs := []struct {
   179  		input schema.GroupVersionResource
   180  		want  schema.GroupVersionResource
   181  	}{
   182  		{
   183  			input: schema.GroupVersionResource{
   184  				Resource: "pods",
   185  			},
   186  			want: schema.GroupVersionResource{
   187  				Version:  "v1",
   188  				Resource: "pods",
   189  			},
   190  		},
   191  		{
   192  			input: schema.GroupVersionResource{
   193  				Version:  "v1",
   194  				Resource: "pods",
   195  			},
   196  			want: schema.GroupVersionResource{
   197  				Version:  "v1",
   198  				Resource: "pods",
   199  			},
   200  		},
   201  		{
   202  			input: schema.GroupVersionResource{
   203  				Version:  "v2",
   204  				Resource: "pods",
   205  			},
   206  			want: schema.GroupVersionResource{
   207  				Version:  "v2",
   208  				Resource: "pods",
   209  			},
   210  		},
   211  		{
   212  			input: schema.GroupVersionResource{
   213  				Resource: "pods",
   214  			},
   215  			want: schema.GroupVersionResource{
   216  				Version:  "v1",
   217  				Resource: "pods",
   218  			},
   219  		},
   220  		{
   221  			input: schema.GroupVersionResource{
   222  				Resource: "jobs",
   223  			},
   224  			want: schema.GroupVersionResource{
   225  				Group:    "extensions",
   226  				Version:  "v1beta",
   227  				Resource: "jobs",
   228  			},
   229  		},
   230  	}
   231  
   232  	for _, tc := range resourceTCs {
   233  		got, err := restMapper.ResourceFor(tc.input)
   234  		if err != nil {
   235  			t.Errorf("ResourceFor(%#v) unexpected error: %v", tc.input, err)
   236  			continue
   237  		}
   238  
   239  		if !reflect.DeepEqual(got, tc.want) {
   240  			t.Errorf("ResourceFor(%#v) = %#v, want %#v", tc.input, got, tc.want)
   241  		}
   242  	}
   243  }
   244  
   245  func TestDeferredDiscoveryRESTMapper_CacheMiss(t *testing.T) {
   246  	assert := assert.New(t)
   247  
   248  	cdc := fakeCachedDiscoveryInterface{fresh: false}
   249  	m := NewDeferredDiscoveryRESTMapper(&cdc)
   250  	assert.False(cdc.fresh, "should NOT be fresh after instantiation")
   251  	assert.Zero(cdc.invalidateCalls, "should not have called Invalidate()")
   252  
   253  	gvk, err := m.KindFor(schema.GroupVersionResource{
   254  		Group:    "a",
   255  		Version:  "v1",
   256  		Resource: "foo",
   257  	})
   258  	assert.NoError(err)
   259  	assert.True(cdc.fresh, "should be fresh after a cache-miss")
   260  	assert.Equal(cdc.invalidateCalls, 1, "should have called Invalidate() once")
   261  	assert.Equal(gvk.Kind, "Foo")
   262  
   263  	gvk, err = m.KindFor(schema.GroupVersionResource{
   264  		Group:    "a",
   265  		Version:  "v1",
   266  		Resource: "foo",
   267  	})
   268  	assert.NoError(err)
   269  	assert.Equal(cdc.invalidateCalls, 1, "should NOT have called Invalidate() again")
   270  
   271  	gvk, err = m.KindFor(schema.GroupVersionResource{
   272  		Group:    "a",
   273  		Version:  "v1",
   274  		Resource: "bar",
   275  	})
   276  	assert.Error(err)
   277  	assert.Equal(cdc.invalidateCalls, 1, "should NOT have called Invalidate() again after another cache-miss, but with fresh==true")
   278  
   279  	cdc.fresh = false
   280  	gvk, err = m.KindFor(schema.GroupVersionResource{
   281  		Group:    "a",
   282  		Version:  "v1",
   283  		Resource: "bar",
   284  	})
   285  	assert.Error(err)
   286  	assert.Equal(cdc.invalidateCalls, 2, "should HAVE called Invalidate() again after another cache-miss, but with fresh==false")
   287  }
   288  
   289  func TestGetAPIGroupResources(t *testing.T) {
   290  	type Test struct {
   291  		name string
   292  
   293  		discovery DiscoveryInterface
   294  
   295  		expected      []*APIGroupResources
   296  		expectedError error
   297  	}
   298  
   299  	for _, test := range []Test{
   300  		{"nil", &fakeFailingDiscovery{nil, nil, nil, nil}, nil, nil},
   301  		{"normal",
   302  			&fakeFailingDiscovery{
   303  				[]metav1.APIGroup{aGroup, bGroup}, nil,
   304  				map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, nil,
   305  			},
   306  			[]*APIGroupResources{
   307  				{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
   308  				{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
   309  			}, nil,
   310  		},
   311  		{"groups failed, but has fallback with a only",
   312  			&fakeFailingDiscovery{
   313  				[]metav1.APIGroup{aGroup}, fmt.Errorf("error fetching groups"),
   314  				map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, nil,
   315  			},
   316  			[]*APIGroupResources{
   317  				{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
   318  			}, nil,
   319  		},
   320  		{"groups failed, but has no fallback",
   321  			&fakeFailingDiscovery{
   322  				nil, fmt.Errorf("error fetching groups"),
   323  				map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, nil,
   324  			},
   325  			nil, fmt.Errorf("error fetching groups"),
   326  		},
   327  		{"a failed, but has fallback",
   328  			&fakeFailingDiscovery{
   329  				[]metav1.APIGroup{aGroup, bGroup}, nil,
   330  				map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, map[string]error{"a/v1": fmt.Errorf("a failed")},
   331  			},
   332  			[]*APIGroupResources{
   333  				{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
   334  				{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
   335  			}, nil, // TODO: do we want this?
   336  		},
   337  		{"a failed, but has no fallback",
   338  			&fakeFailingDiscovery{
   339  				[]metav1.APIGroup{aGroup, bGroup}, nil,
   340  				map[string]*metav1.APIResourceList{"b/v1": &bResources}, map[string]error{"a/v1": fmt.Errorf("a failed")},
   341  			},
   342  			[]*APIGroupResources{
   343  				{aGroup, map[string][]metav1.APIResource{}},
   344  				{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
   345  			}, nil, // TODO: do we want this?
   346  		},
   347  		{"a and b failed, but have fallbacks",
   348  			&fakeFailingDiscovery{
   349  				[]metav1.APIGroup{aGroup, bGroup}, nil,
   350  				map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, // TODO: both fallbacks are ignored
   351  				map[string]error{"a/v1": fmt.Errorf("a failed"), "b/v1": fmt.Errorf("b failed")},
   352  			},
   353  			[]*APIGroupResources{
   354  				{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
   355  				{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
   356  			}, nil, // TODO: do we want this?
   357  		},
   358  	} {
   359  		t.Run(test.name, func(t *testing.T) {
   360  			got, err := GetAPIGroupResources(test.discovery)
   361  			if err == nil && test.expectedError != nil {
   362  				t.Fatalf("expected error %q, but got none", test.expectedError)
   363  			} else if err != nil && test.expectedError == nil {
   364  				t.Fatalf("unexpected error: %v", err)
   365  			}
   366  			if !reflect.DeepEqual(test.expected, got) {
   367  				t.Errorf("unexpected result:\nexpected = %s\ngot = %s", dump.Pretty(test.expected), dump.Pretty(got))
   368  			}
   369  		})
   370  	}
   371  
   372  }
   373  
   374  var _ DiscoveryInterface = &fakeFailingDiscovery{}
   375  
   376  type fakeFailingDiscovery struct {
   377  	groups    []metav1.APIGroup
   378  	groupsErr error
   379  
   380  	resourcesForGroupVersion    map[string]*metav1.APIResourceList
   381  	resourcesForGroupVersionErr map[string]error
   382  }
   383  
   384  func (*fakeFailingDiscovery) RESTClient() restclient.Interface {
   385  	return nil
   386  }
   387  
   388  func (d *fakeFailingDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
   389  	if d.groups == nil && d.groupsErr != nil {
   390  		return nil, d.groupsErr
   391  	}
   392  	return &metav1.APIGroupList{Groups: d.groups}, d.groupsErr
   393  }
   394  
   395  func (d *fakeFailingDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
   396  	return ServerGroupsAndResources(d)
   397  }
   398  func (d *fakeFailingDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
   399  	if rs, found := d.resourcesForGroupVersion[groupVersion]; found {
   400  		return rs, d.resourcesForGroupVersionErr[groupVersion]
   401  	}
   402  	return nil, fmt.Errorf("not found")
   403  }
   404  
   405  func (d *fakeFailingDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
   406  	return ServerPreferredResources(d)
   407  }
   408  
   409  func (d *fakeFailingDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
   410  	return ServerPreferredNamespacedResources(d)
   411  }
   412  
   413  func (*fakeFailingDiscovery) ServerVersion() (*version.Info, error) {
   414  	return &version.Info{}, nil
   415  }
   416  
   417  func (*fakeFailingDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
   418  	panic("implement me")
   419  }
   420  
   421  func (c *fakeFailingDiscovery) OpenAPIV3() openapi.Client {
   422  	panic("implement me")
   423  }
   424  
   425  func (c *fakeFailingDiscovery) WithLegacy() DiscoveryInterface {
   426  	panic("implement me")
   427  }
   428  
   429  type fakeCachedDiscoveryInterface struct {
   430  	invalidateCalls int
   431  	fresh           bool
   432  	enabledGroupA   bool
   433  }
   434  
   435  var _ CachedDiscoveryInterface = &fakeCachedDiscoveryInterface{}
   436  
   437  func (c *fakeCachedDiscoveryInterface) Fresh() bool {
   438  	return c.fresh
   439  }
   440  
   441  func (c *fakeCachedDiscoveryInterface) Invalidate() {
   442  	c.invalidateCalls = c.invalidateCalls + 1
   443  	c.fresh = true
   444  	c.enabledGroupA = true
   445  }
   446  
   447  func (c *fakeCachedDiscoveryInterface) RESTClient() restclient.Interface {
   448  	return &fake.RESTClient{}
   449  }
   450  
   451  func (c *fakeCachedDiscoveryInterface) ServerGroups() (*metav1.APIGroupList, error) {
   452  	if c.enabledGroupA {
   453  		return &metav1.APIGroupList{
   454  			Groups: []metav1.APIGroup{aGroup},
   455  		}, nil
   456  	}
   457  	return &metav1.APIGroupList{}, nil
   458  }
   459  
   460  func (c *fakeCachedDiscoveryInterface) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
   461  	return ServerGroupsAndResources(c)
   462  }
   463  
   464  func (c *fakeCachedDiscoveryInterface) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
   465  	if c.enabledGroupA && groupVersion == "a/v1" {
   466  		return &aResources, nil
   467  	}
   468  
   469  	return nil, errors.NewNotFound(schema.GroupResource{}, "")
   470  }
   471  
   472  func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
   473  	if c.enabledGroupA {
   474  		return []*metav1.APIResourceList{
   475  			{
   476  				GroupVersion: "a/v1",
   477  				APIResources: []metav1.APIResource{
   478  					{
   479  						Name:  "foo",
   480  						Kind:  "Foo",
   481  						Verbs: []string{},
   482  					},
   483  				},
   484  			},
   485  		}, nil
   486  	}
   487  	return nil, nil
   488  }
   489  
   490  func (c *fakeCachedDiscoveryInterface) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
   491  	return nil, nil
   492  }
   493  
   494  func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) {
   495  	return &version.Info{}, nil
   496  }
   497  
   498  func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*openapi_v2.Document, error) {
   499  	return &openapi_v2.Document{}, nil
   500  }
   501  
   502  func (c *fakeCachedDiscoveryInterface) OpenAPIV3() openapi.Client {
   503  	panic("implement me")
   504  }
   505  
   506  func (c *fakeCachedDiscoveryInterface) WithLegacy() DiscoveryInterface {
   507  	panic("implement me")
   508  }
   509  
   510  var (
   511  	aGroup = metav1.APIGroup{
   512  		Name: "a",
   513  		Versions: []metav1.GroupVersionForDiscovery{
   514  			{
   515  				GroupVersion: "a/v1",
   516  				Version:      "v1",
   517  			},
   518  		},
   519  		PreferredVersion: metav1.GroupVersionForDiscovery{
   520  			GroupVersion: "a/v1",
   521  			Version:      "v1",
   522  		},
   523  	}
   524  	bGroup = metav1.APIGroup{
   525  		Name: "b",
   526  		Versions: []metav1.GroupVersionForDiscovery{
   527  			{
   528  				GroupVersion: "b/v1",
   529  				Version:      "v1",
   530  			},
   531  		},
   532  		PreferredVersion: metav1.GroupVersionForDiscovery{
   533  			GroupVersion: "b/v1",
   534  			Version:      "v1",
   535  		},
   536  	}
   537  	aResources = metav1.APIResourceList{
   538  		GroupVersion: "a/v1",
   539  		APIResources: []metav1.APIResource{aFoo},
   540  	}
   541  	aFoo = metav1.APIResource{
   542  		Name:       "foo",
   543  		Kind:       "Foo",
   544  		Namespaced: false,
   545  	}
   546  	bResources = metav1.APIResourceList{
   547  		GroupVersion: "b/v1",
   548  		APIResources: []metav1.APIResource{bBar},
   549  	}
   550  	bBar = metav1.APIResource{
   551  		Name:       "bar",
   552  		Kind:       "Bar",
   553  		Namespaced: true,
   554  	}
   555  )
   556  

View as plain text