...

Source file src/k8s.io/client-go/discovery/cached/disk/cached_discovery_test.go

Documentation: k8s.io/client-go/discovery/cached/disk

     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 disk
    18  
    19  import (
    20  	"encoding/json"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	openapi_v2 "github.com/google/gnostic-models/openapiv2"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  
    33  	apidiscovery "k8s.io/api/apidiscovery/v2"
    34  	"k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/schema"
    38  	"k8s.io/apimachinery/pkg/util/sets"
    39  	"k8s.io/apimachinery/pkg/version"
    40  	"k8s.io/client-go/discovery"
    41  	"k8s.io/client-go/openapi"
    42  	restclient "k8s.io/client-go/rest"
    43  	"k8s.io/client-go/rest/fake"
    44  	testutil "k8s.io/client-go/util/testing"
    45  )
    46  
    47  func TestCachedDiscoveryClient_Fresh(t *testing.T) {
    48  	assert := assert.New(t)
    49  
    50  	d, err := os.MkdirTemp("", "")
    51  	assert.NoError(err)
    52  	defer os.RemoveAll(d)
    53  
    54  	c := fakeDiscoveryClient{}
    55  	cdc := newCachedDiscoveryClient(&c, d, 60*time.Second)
    56  	assert.True(cdc.Fresh(), "should be fresh after creation")
    57  
    58  	cdc.ServerGroups()
    59  	assert.True(cdc.Fresh(), "should be fresh after groups call without cache")
    60  	assert.Equal(c.groupCalls, 1)
    61  
    62  	cdc.ServerGroups()
    63  	assert.True(cdc.Fresh(), "should be fresh after another groups call")
    64  	assert.Equal(c.groupCalls, 1)
    65  
    66  	cdc.ServerGroupsAndResources()
    67  	assert.True(cdc.Fresh(), "should be fresh after resources call")
    68  	assert.Equal(c.resourceCalls, 1)
    69  
    70  	cdc.ServerGroupsAndResources()
    71  	assert.True(cdc.Fresh(), "should be fresh after another resources call")
    72  	assert.Equal(c.resourceCalls, 1)
    73  
    74  	cdc = newCachedDiscoveryClient(&c, d, 60*time.Second)
    75  	cdc.ServerGroups()
    76  	assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing groups cache")
    77  	assert.Equal(c.groupCalls, 1)
    78  
    79  	cdc.ServerGroupsAndResources()
    80  	assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing resources cache")
    81  	assert.Equal(c.resourceCalls, 1)
    82  
    83  	cdc.Invalidate()
    84  	assert.True(cdc.Fresh(), "should be fresh after cache invalidation")
    85  
    86  	cdc.ServerGroupsAndResources()
    87  	assert.True(cdc.Fresh(), "should ignore existing resources cache after invalidation")
    88  	assert.Equal(c.resourceCalls, 2)
    89  }
    90  
    91  func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
    92  	assert := assert.New(t)
    93  
    94  	d, err := os.MkdirTemp("", "")
    95  	assert.NoError(err)
    96  	defer os.RemoveAll(d)
    97  
    98  	c := fakeDiscoveryClient{}
    99  	cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
   100  	cdc.ServerGroups()
   101  	assert.Equal(c.groupCalls, 1)
   102  
   103  	time.Sleep(1 * time.Second)
   104  
   105  	cdc.ServerGroups()
   106  	assert.Equal(c.groupCalls, 2)
   107  }
   108  
   109  func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
   110  	assert := assert.New(t)
   111  
   112  	d, err := os.MkdirTemp("", "")
   113  	assert.NoError(err)
   114  	os.RemoveAll(d)
   115  	defer os.RemoveAll(d)
   116  
   117  	c := fakeDiscoveryClient{}
   118  	cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
   119  	cdc.ServerGroups()
   120  
   121  	err = filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
   122  		if err != nil {
   123  			return err
   124  		}
   125  		if info.IsDir() {
   126  			assert.Equal(os.FileMode(0750), info.Mode().Perm())
   127  		} else {
   128  			assert.Equal(os.FileMode(0660), info.Mode().Perm())
   129  		}
   130  		return nil
   131  	})
   132  	assert.NoError(err)
   133  }
   134  
   135  // Tests that schema instances returned by openapi cached and returned after
   136  // successive calls
   137  func TestOpenAPIDiskCache(t *testing.T) {
   138  	// Create discovery cache dir (unused)
   139  	discoCache, err := os.MkdirTemp("", "test-cached-discovery-client-disco-*")
   140  	require.NoError(t, err)
   141  	os.RemoveAll(discoCache)
   142  	defer os.RemoveAll(discoCache)
   143  
   144  	// Create http cache dir
   145  	httpCache, err := os.MkdirTemp("", "test-cached-discovery-client-http-*")
   146  	require.NoError(t, err)
   147  	os.RemoveAll(httpCache)
   148  	defer os.RemoveAll(httpCache)
   149  
   150  	// Start test OpenAPI server
   151  	fakeServer, err := testutil.NewFakeOpenAPIV3Server("../../testdata")
   152  	require.NoError(t, err)
   153  	defer fakeServer.HttpServer.Close()
   154  
   155  	require.Greater(t, len(fakeServer.ServedDocuments), 0)
   156  
   157  	client, err := NewCachedDiscoveryClientForConfig(
   158  		&restclient.Config{Host: fakeServer.HttpServer.URL},
   159  		discoCache,
   160  		httpCache,
   161  		1*time.Nanosecond,
   162  	)
   163  	require.NoError(t, err)
   164  
   165  	openapiClient := client.OpenAPIV3()
   166  
   167  	// Ensure initial Paths call hits server
   168  	_, err = openapiClient.Paths()
   169  	require.NoError(t, err)
   170  	assert.Equal(t, 1, fakeServer.RequestCounters["/openapi/v3"])
   171  
   172  	// Ensure Paths call does hits server again
   173  	// This is expected since openapiClient is the same instance, so Paths()
   174  	// should be cached in memory.
   175  	paths, err := openapiClient.Paths()
   176  	require.NoError(t, err)
   177  	assert.Equal(t, 1, fakeServer.RequestCounters["/openapi/v3"])
   178  	require.Greater(t, len(paths), 0)
   179  
   180  	contentTypes := []string{
   181  		runtime.ContentTypeJSON, openapi.ContentTypeOpenAPIV3PB,
   182  	}
   183  
   184  	for _, contentType := range contentTypes {
   185  		t.Run(contentType, func(t *testing.T) {
   186  			// Reset all counters (cant just reset to nil since reference is shared)
   187  			for k := range fakeServer.RequestCounters {
   188  				delete(fakeServer.RequestCounters, k)
   189  			}
   190  
   191  			i := 0
   192  			for k, v := range paths {
   193  				i++
   194  
   195  				_, err = v.Schema(contentType)
   196  				assert.NoError(t, err)
   197  
   198  				path := "/openapi/v3/" + strings.TrimPrefix(k, "/")
   199  				assert.Equal(t, 1, fakeServer.RequestCounters[path])
   200  
   201  				// Ensure schema call is served from memory
   202  				_, err = v.Schema(contentType)
   203  				assert.NoError(t, err)
   204  				assert.Equal(t, 1, fakeServer.RequestCounters[path])
   205  
   206  				client.Invalidate()
   207  
   208  				// Refetch the schema from a new openapi client to try to force a new
   209  				// http request
   210  				newPaths, err := client.OpenAPIV3().Paths()
   211  				if !assert.NoError(t, err) {
   212  					continue
   213  				}
   214  
   215  				// Ensure schema call is still served from disk
   216  				_, err = newPaths[k].Schema(contentType)
   217  				assert.NoError(t, err)
   218  				assert.Equal(t, 1, fakeServer.RequestCounters[path])
   219  			}
   220  		})
   221  	}
   222  
   223  }
   224  
   225  // Tests function "ServerGroups" when the "unaggregated" discovery is returned.
   226  func TestCachedDiscoveryClientUnaggregatedServerGroups(t *testing.T) {
   227  	tests := []struct {
   228  		name                  string
   229  		corev1                *metav1.APIVersions
   230  		apis                  *metav1.APIGroupList
   231  		expectedGroupNames    []string
   232  		expectedGroupVersions []string
   233  	}{
   234  		{
   235  			name: "Legacy discovery format: 1 version at /api, 1 group at /apis",
   236  			corev1: &metav1.APIVersions{
   237  				Versions: []string{
   238  					"v1",
   239  				},
   240  			},
   241  			apis: &metav1.APIGroupList{
   242  				Groups: []metav1.APIGroup{
   243  					{
   244  						Name: "extensions",
   245  						Versions: []metav1.GroupVersionForDiscovery{
   246  							{GroupVersion: "extensions/v1beta1"},
   247  						},
   248  					},
   249  				},
   250  			},
   251  			expectedGroupNames:    []string{"", "extensions"},
   252  			expectedGroupVersions: []string{"v1", "extensions/v1beta1"},
   253  		},
   254  		{
   255  			name: "Legacy discovery format: 1 version at /api, 2 groups/1 version at /apis",
   256  			corev1: &metav1.APIVersions{
   257  				Versions: []string{
   258  					"v1",
   259  				},
   260  			},
   261  			apis: &metav1.APIGroupList{
   262  				Groups: []metav1.APIGroup{
   263  					{
   264  						Name: "apps",
   265  						Versions: []metav1.GroupVersionForDiscovery{
   266  							{GroupVersion: "apps/v1"},
   267  						},
   268  					},
   269  					{
   270  						Name: "extensions",
   271  						Versions: []metav1.GroupVersionForDiscovery{
   272  							{GroupVersion: "extensions/v1beta1"},
   273  						},
   274  					},
   275  				},
   276  			},
   277  			expectedGroupNames:    []string{"", "apps", "extensions"},
   278  			expectedGroupVersions: []string{"v1", "apps/v1", "extensions/v1beta1"},
   279  		},
   280  		{
   281  			name: "Legacy discovery format: 1 version at /api, 2 groups/2 versions at /apis",
   282  			corev1: &metav1.APIVersions{
   283  				Versions: []string{
   284  					"v1",
   285  				},
   286  			},
   287  			apis: &metav1.APIGroupList{
   288  				Groups: []metav1.APIGroup{
   289  					{
   290  						Name: "batch",
   291  						Versions: []metav1.GroupVersionForDiscovery{
   292  							{GroupVersion: "batch/v1"},
   293  						},
   294  					},
   295  					{
   296  						Name: "batch",
   297  						Versions: []metav1.GroupVersionForDiscovery{
   298  							{GroupVersion: "batch/v1beta1"},
   299  						},
   300  					},
   301  					{
   302  						Name: "extensions",
   303  						Versions: []metav1.GroupVersionForDiscovery{
   304  							{GroupVersion: "extensions/v1beta1"},
   305  						},
   306  					},
   307  					{
   308  						Name: "extensions",
   309  						Versions: []metav1.GroupVersionForDiscovery{
   310  							{GroupVersion: "extensions/v1alpha1"},
   311  						},
   312  					},
   313  				},
   314  			},
   315  			expectedGroupNames: []string{
   316  				"",
   317  				"batch",
   318  				"extensions",
   319  			},
   320  			expectedGroupVersions: []string{
   321  				"v1",
   322  				"batch/v1",
   323  				"batch/v1beta1",
   324  				"extensions/v1beta1",
   325  				"extensions/v1alpha1",
   326  			},
   327  		},
   328  	}
   329  
   330  	for _, test := range tests {
   331  		// Create discovery cache dir
   332  		discoCache, err := os.MkdirTemp("", "test-cached-discovery-client-disco-*")
   333  		require.NoError(t, err)
   334  		os.RemoveAll(discoCache)
   335  		defer os.RemoveAll(discoCache)
   336  		// Create http cache dir (unused)
   337  		httpCache, err := os.MkdirTemp("", "test-cached-discovery-client-http-*")
   338  		require.NoError(t, err)
   339  		os.RemoveAll(httpCache)
   340  		defer os.RemoveAll(httpCache)
   341  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   342  			var body interface{}
   343  			switch req.URL.Path {
   344  			case "/api":
   345  				body = test.corev1
   346  			case "/apis":
   347  				body = test.apis
   348  			default:
   349  				w.WriteHeader(http.StatusNotFound)
   350  				return
   351  			}
   352  			output, err := json.Marshal(body)
   353  			require.NoError(t, err)
   354  			// Content-type is "unaggregated" discovery format -- no resources returned.
   355  			w.Header().Set("Content-Type", discovery.AcceptV1)
   356  			w.WriteHeader(http.StatusOK)
   357  			w.Write(output)
   358  		}))
   359  		defer server.Close()
   360  		client, err := NewCachedDiscoveryClientForConfig(
   361  			&restclient.Config{Host: server.URL},
   362  			discoCache,
   363  			httpCache,
   364  			1*time.Nanosecond,
   365  		)
   366  		require.NoError(t, err)
   367  		apiGroupList, err := client.ServerGroups()
   368  		require.NoError(t, err)
   369  		// Discovery groups cached in servergroups.json file.
   370  		numFound, err := numFilesFound(discoCache, "servergroups.json")
   371  		assert.NoError(t, err)
   372  		assert.Equal(t, 1, numFound,
   373  			"%s: expected 1 discovery cache file servergroups.json found, got %d", test.name, numFound)
   374  		// Test expected groups returned by server groups.
   375  		expectedGroupNames := sets.NewString(test.expectedGroupNames...)
   376  		actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...)
   377  		assert.True(t, expectedGroupNames.Equal(actualGroupNames),
   378  			"%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
   379  		// Test the expected group versions for the aggregated discovery is correct.
   380  		expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
   381  		actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...)
   382  		assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
   383  			"%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
   384  	}
   385  }
   386  
   387  // Aggregated discovery format returned
   388  func TestCachedDiscoveryClientAggregatedServerGroups(t *testing.T) {
   389  	tests := []struct {
   390  		name                      string
   391  		corev1                    *apidiscovery.APIGroupDiscoveryList
   392  		apis                      *apidiscovery.APIGroupDiscoveryList
   393  		expectedGroupNames        []string
   394  		expectedGroupVersions     []string
   395  		expectedPreferredVersions []string
   396  	}{
   397  		{
   398  			name: "Aggregated cached discovery: 1 group/1 version at /api, 1 group/1 version at /apis",
   399  			corev1: &apidiscovery.APIGroupDiscoveryList{
   400  				Items: []apidiscovery.APIGroupDiscovery{
   401  					{
   402  						Versions: []apidiscovery.APIVersionDiscovery{
   403  							{
   404  								Version: "v1",
   405  								Resources: []apidiscovery.APIResourceDiscovery{
   406  									{
   407  										Resource: "pods",
   408  										ResponseKind: &metav1.GroupVersionKind{
   409  											Group:   "",
   410  											Version: "v1",
   411  											Kind:    "Pod",
   412  										},
   413  										Scope: apidiscovery.ScopeNamespace,
   414  									},
   415  								},
   416  							},
   417  						},
   418  					},
   419  				},
   420  			},
   421  			apis: &apidiscovery.APIGroupDiscoveryList{
   422  				Items: []apidiscovery.APIGroupDiscovery{
   423  					{
   424  						ObjectMeta: metav1.ObjectMeta{
   425  							Name: "apps",
   426  						},
   427  						Versions: []apidiscovery.APIVersionDiscovery{
   428  							{
   429  								Version: "v1",
   430  								Resources: []apidiscovery.APIResourceDiscovery{
   431  									{
   432  										Resource: "deployments",
   433  										ResponseKind: &metav1.GroupVersionKind{
   434  											Group:   "apps",
   435  											Version: "v1",
   436  											Kind:    "Deployment",
   437  										},
   438  										Scope: apidiscovery.ScopeNamespace,
   439  									},
   440  								},
   441  							},
   442  						},
   443  					},
   444  				},
   445  			},
   446  			expectedGroupNames:        []string{"", "apps"},
   447  			expectedGroupVersions:     []string{"v1", "apps/v1"},
   448  			expectedPreferredVersions: []string{"v1", "apps/v1"},
   449  		},
   450  		{
   451  			name: "Aggregated discovery: 1 group/1 version at /api, 1 group/2 versions at /apis",
   452  			corev1: &apidiscovery.APIGroupDiscoveryList{
   453  				Items: []apidiscovery.APIGroupDiscovery{
   454  					{
   455  						ObjectMeta: metav1.ObjectMeta{
   456  							Name: "",
   457  						},
   458  						Versions: []apidiscovery.APIVersionDiscovery{
   459  							{
   460  								Version: "v1",
   461  								Resources: []apidiscovery.APIResourceDiscovery{
   462  									{
   463  										Resource: "pods",
   464  										ResponseKind: &metav1.GroupVersionKind{
   465  											Group:   "",
   466  											Version: "v1",
   467  											Kind:    "Pod",
   468  										},
   469  										Scope: apidiscovery.ScopeNamespace,
   470  									},
   471  								},
   472  							},
   473  						},
   474  					},
   475  				},
   476  			},
   477  			apis: &apidiscovery.APIGroupDiscoveryList{
   478  				Items: []apidiscovery.APIGroupDiscovery{
   479  					{
   480  						ObjectMeta: metav1.ObjectMeta{
   481  							Name: "apps",
   482  						},
   483  						Versions: []apidiscovery.APIVersionDiscovery{
   484  							// v2 is preferred since it is first
   485  							{
   486  								Version: "v2",
   487  								Resources: []apidiscovery.APIResourceDiscovery{
   488  									{
   489  										Resource: "deployments",
   490  										ResponseKind: &metav1.GroupVersionKind{
   491  											Group:   "apps",
   492  											Version: "v2",
   493  											Kind:    "Deployment",
   494  										},
   495  										Scope: apidiscovery.ScopeNamespace,
   496  									},
   497  								},
   498  							},
   499  							{
   500  								Version: "v1",
   501  								Resources: []apidiscovery.APIResourceDiscovery{
   502  									{
   503  										Resource: "deployments",
   504  										ResponseKind: &metav1.GroupVersionKind{
   505  											Group:   "apps",
   506  											Version: "v1",
   507  											Kind:    "Deployment",
   508  										},
   509  										Scope: apidiscovery.ScopeNamespace,
   510  									},
   511  								},
   512  							},
   513  						},
   514  					},
   515  				},
   516  			},
   517  			expectedGroupNames:        []string{"", "apps"},
   518  			expectedGroupVersions:     []string{"v1", "apps/v1", "apps/v2"},
   519  			expectedPreferredVersions: []string{"v1", "apps/v2"},
   520  		},
   521  		{
   522  			name:   "Aggregated discovery: /api returns nothing, 2 groups at /apis",
   523  			corev1: &apidiscovery.APIGroupDiscoveryList{},
   524  			apis: &apidiscovery.APIGroupDiscoveryList{
   525  				Items: []apidiscovery.APIGroupDiscovery{
   526  					{
   527  						ObjectMeta: metav1.ObjectMeta{
   528  							Name: "apps",
   529  						},
   530  						Versions: []apidiscovery.APIVersionDiscovery{
   531  							{
   532  								Version: "v1",
   533  								Resources: []apidiscovery.APIResourceDiscovery{
   534  									{
   535  										Resource: "deployments",
   536  										ResponseKind: &metav1.GroupVersionKind{
   537  											Group:   "apps",
   538  											Version: "v1",
   539  											Kind:    "Deployment",
   540  										},
   541  										Scope: apidiscovery.ScopeNamespace,
   542  									},
   543  									{
   544  										Resource: "statefulsets",
   545  										ResponseKind: &metav1.GroupVersionKind{
   546  											Group:   "apps",
   547  											Version: "v1",
   548  											Kind:    "StatefulSet",
   549  										},
   550  										Scope: apidiscovery.ScopeNamespace,
   551  									},
   552  								},
   553  							},
   554  						},
   555  					},
   556  					{
   557  						ObjectMeta: metav1.ObjectMeta{
   558  							Name: "batch",
   559  						},
   560  						Versions: []apidiscovery.APIVersionDiscovery{
   561  							// v1 is preferred since it is first
   562  							{
   563  								Version: "v1",
   564  								Resources: []apidiscovery.APIResourceDiscovery{
   565  									{
   566  										Resource: "jobs",
   567  										ResponseKind: &metav1.GroupVersionKind{
   568  											Group:   "batch",
   569  											Version: "v1",
   570  											Kind:    "Job",
   571  										},
   572  										Scope: apidiscovery.ScopeNamespace,
   573  									},
   574  									{
   575  										Resource: "cronjobs",
   576  										ResponseKind: &metav1.GroupVersionKind{
   577  											Group:   "batch",
   578  											Version: "v1",
   579  											Kind:    "CronJob",
   580  										},
   581  										Scope: apidiscovery.ScopeNamespace,
   582  									},
   583  								},
   584  							},
   585  							{
   586  								Version: "v1beta1",
   587  								Resources: []apidiscovery.APIResourceDiscovery{
   588  									{
   589  										Resource: "jobs",
   590  										ResponseKind: &metav1.GroupVersionKind{
   591  											Group:   "batch",
   592  											Version: "v1beta1",
   593  											Kind:    "Job",
   594  										},
   595  										Scope: apidiscovery.ScopeNamespace,
   596  									},
   597  									{
   598  										Resource: "cronjobs",
   599  										ResponseKind: &metav1.GroupVersionKind{
   600  											Group:   "batch",
   601  											Version: "v1beta1",
   602  											Kind:    "CronJob",
   603  										},
   604  										Scope: apidiscovery.ScopeNamespace,
   605  									},
   606  								},
   607  							},
   608  						},
   609  					},
   610  				},
   611  			},
   612  			expectedGroupNames:        []string{"apps", "batch"},
   613  			expectedGroupVersions:     []string{"apps/v1", "batch/v1", "batch/v1beta1"},
   614  			expectedPreferredVersions: []string{"apps/v1", "batch/v1"},
   615  		},
   616  	}
   617  
   618  	for _, test := range tests {
   619  		// Create discovery cache dir
   620  		discoCache, err := os.MkdirTemp("", "test-cached-discovery-client-disco-*")
   621  		require.NoError(t, err)
   622  		os.RemoveAll(discoCache)
   623  		defer os.RemoveAll(discoCache)
   624  		// Create http cache dir (unused)
   625  		httpCache, err := os.MkdirTemp("", "test-cached-discovery-client-http-*")
   626  		require.NoError(t, err)
   627  		os.RemoveAll(httpCache)
   628  		defer os.RemoveAll(httpCache)
   629  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   630  			var agg *apidiscovery.APIGroupDiscoveryList
   631  			switch req.URL.Path {
   632  			case "/api":
   633  				agg = test.corev1
   634  			case "/apis":
   635  				agg = test.apis
   636  			default:
   637  				w.WriteHeader(http.StatusNotFound)
   638  				return
   639  			}
   640  			output, err := json.Marshal(agg)
   641  			if err != nil {
   642  				t.Fatalf("unexpected encoding error: %v", err)
   643  				return
   644  			}
   645  			// Content-type is "aggregated" discovery format.
   646  			w.Header().Set("Content-Type", discovery.AcceptV2)
   647  			w.WriteHeader(http.StatusOK)
   648  			w.Write(output)
   649  		}))
   650  		defer server.Close()
   651  		client, err := NewCachedDiscoveryClientForConfig(
   652  			&restclient.Config{Host: server.URL},
   653  			discoCache,
   654  			httpCache,
   655  			1*time.Nanosecond,
   656  		)
   657  		require.NoError(t, err)
   658  		apiGroupList, err := client.ServerGroups()
   659  		require.NoError(t, err)
   660  		// Discovery groups cached in servergroups.json file.
   661  		numFound, err := numFilesFound(discoCache, "servergroups.json")
   662  		assert.NoError(t, err)
   663  		assert.Equal(t, 1, numFound,
   664  			"%s: expected 1 discovery cache file servergroups.json found, got %d", test.name, numFound)
   665  		// Test expected groups returned by server groups.
   666  		expectedGroupNames := sets.NewString(test.expectedGroupNames...)
   667  		actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...)
   668  		assert.True(t, expectedGroupNames.Equal(actualGroupNames),
   669  			"%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
   670  		// Test the expected group versions for the aggregated discovery is correct.
   671  		expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
   672  		actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...)
   673  		assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
   674  			"%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
   675  		// Test the groups preferred version is correct.
   676  		expectedPreferredVersions := sets.NewString(test.expectedPreferredVersions...)
   677  		actualPreferredVersions := sets.NewString(preferredVersionsFromList(apiGroupList)...)
   678  		assert.True(t, expectedPreferredVersions.Equal(actualPreferredVersions),
   679  			"%s: Expected preferred group/version (%s), got (%s)", test.name, expectedPreferredVersions.List(), actualPreferredVersions.List())
   680  	}
   681  }
   682  
   683  func numFilesFound(dir string, filename string) (int, error) {
   684  	numFound := 0
   685  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   686  		if err != nil {
   687  			return err
   688  		}
   689  		if info.Name() == filename {
   690  			numFound++
   691  		}
   692  		return nil
   693  	})
   694  	if err != nil {
   695  		return 0, err
   696  	}
   697  	return numFound, nil
   698  }
   699  
   700  type fakeDiscoveryClient struct {
   701  	groupCalls    int
   702  	resourceCalls int
   703  	versionCalls  int
   704  	openAPICalls  int
   705  
   706  	serverResourcesHandler func() ([]*metav1.APIResourceList, error)
   707  }
   708  
   709  var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
   710  
   711  func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
   712  	return &fake.RESTClient{}
   713  }
   714  
   715  func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
   716  	c.groupCalls = c.groupCalls + 1
   717  	return c.serverGroups()
   718  }
   719  
   720  func (c *fakeDiscoveryClient) serverGroups() (*metav1.APIGroupList, error) {
   721  	return &metav1.APIGroupList{
   722  		Groups: []metav1.APIGroup{
   723  			{
   724  				Name: "a",
   725  				Versions: []metav1.GroupVersionForDiscovery{
   726  					{
   727  						GroupVersion: "a/v1",
   728  						Version:      "v1",
   729  					},
   730  				},
   731  				PreferredVersion: metav1.GroupVersionForDiscovery{
   732  					GroupVersion: "a/v1",
   733  					Version:      "v1",
   734  				},
   735  			},
   736  		},
   737  	}, nil
   738  }
   739  
   740  func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
   741  	c.resourceCalls = c.resourceCalls + 1
   742  	if groupVersion == "a/v1" {
   743  		return &metav1.APIResourceList{APIResources: []metav1.APIResource{{Name: "widgets", Kind: "Widget"}}}, nil
   744  	}
   745  
   746  	return nil, errors.NewNotFound(schema.GroupResource{}, "")
   747  }
   748  
   749  func (c *fakeDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
   750  	c.resourceCalls = c.resourceCalls + 1
   751  
   752  	gs, _ := c.serverGroups()
   753  	resultGroups := []*metav1.APIGroup{}
   754  	for i := range gs.Groups {
   755  		resultGroups = append(resultGroups, &gs.Groups[i])
   756  	}
   757  
   758  	if c.serverResourcesHandler != nil {
   759  		rs, err := c.serverResourcesHandler()
   760  		return resultGroups, rs, err
   761  	}
   762  	return resultGroups, []*metav1.APIResourceList{}, nil
   763  }
   764  
   765  func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
   766  	c.resourceCalls = c.resourceCalls + 1
   767  	return nil, nil
   768  }
   769  
   770  func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
   771  	c.resourceCalls = c.resourceCalls + 1
   772  	return nil, nil
   773  }
   774  
   775  func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
   776  	c.versionCalls = c.versionCalls + 1
   777  	return &version.Info{}, nil
   778  }
   779  
   780  func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
   781  	c.openAPICalls = c.openAPICalls + 1
   782  	return &openapi_v2.Document{}, nil
   783  }
   784  
   785  func (d *fakeDiscoveryClient) OpenAPIV3() openapi.Client {
   786  	panic("unimplemented")
   787  }
   788  
   789  func (d *fakeDiscoveryClient) WithLegacy() discovery.DiscoveryInterface {
   790  	panic("unimplemented")
   791  }
   792  
   793  func groupNamesFromList(groups *metav1.APIGroupList) []string {
   794  	result := []string{}
   795  	for _, group := range groups.Groups {
   796  		result = append(result, group.Name)
   797  	}
   798  	return result
   799  }
   800  
   801  func preferredVersionsFromList(groups *metav1.APIGroupList) []string {
   802  	result := []string{}
   803  	for _, group := range groups.Groups {
   804  		preferredGV := group.PreferredVersion.GroupVersion
   805  		result = append(result, preferredGV)
   806  	}
   807  	return result
   808  }
   809  
   810  func groupVersionsFromGroups(groups *metav1.APIGroupList) []string {
   811  	result := []string{}
   812  	for _, group := range groups.Groups {
   813  		for _, version := range group.Versions {
   814  			result = append(result, version.GroupVersion)
   815  		}
   816  	}
   817  	return result
   818  }
   819  

View as plain text