...

Source file src/k8s.io/client-go/discovery/discovery_client_test.go

Documentation: k8s.io/client-go/discovery

     1  /*
     2  Copyright 2014 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 discovery
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/gogo/protobuf/proto"
    29  	openapi_v2 "github.com/google/gnostic-models/openapiv2"
    30  	openapi_v3 "github.com/google/gnostic-models/openapiv3"
    31  	"github.com/google/go-cmp/cmp"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  	golangproto "google.golang.org/protobuf/proto"
    35  	apidiscovery "k8s.io/api/apidiscovery/v2"
    36  	apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/apimachinery/pkg/runtime"
    39  	"k8s.io/apimachinery/pkg/runtime/schema"
    40  	"k8s.io/apimachinery/pkg/util/sets"
    41  	"k8s.io/apimachinery/pkg/version"
    42  	"k8s.io/client-go/openapi"
    43  	restclient "k8s.io/client-go/rest"
    44  	testutil "k8s.io/client-go/util/testing"
    45  	"k8s.io/kube-openapi/pkg/spec3"
    46  )
    47  
    48  func TestGetServerVersion(t *testing.T) {
    49  	expect := version.Info{
    50  		Major:     "foo",
    51  		Minor:     "bar",
    52  		GitCommit: "baz",
    53  	}
    54  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    55  		output, err := json.Marshal(expect)
    56  		if err != nil {
    57  			t.Errorf("unexpected encoding error: %v", err)
    58  			return
    59  		}
    60  		w.Header().Set("Content-Type", "application/json")
    61  		w.WriteHeader(http.StatusOK)
    62  		_, err = w.Write(output)
    63  		require.NoError(t, err)
    64  	}))
    65  	defer server.Close()
    66  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
    67  
    68  	got, err := client.ServerVersion()
    69  	if err != nil {
    70  		t.Fatalf("unexpected encoding error: %v", err)
    71  	}
    72  	if e, a := expect, *got; !reflect.DeepEqual(e, a) {
    73  		t.Errorf("expected %v, got %v", e, a)
    74  	}
    75  }
    76  
    77  func TestGetServerGroupsWithV1Server(t *testing.T) {
    78  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    79  		var obj interface{}
    80  		switch req.URL.Path {
    81  		case "/api":
    82  			obj = &metav1.APIVersions{
    83  				Versions: []string{
    84  					"v1",
    85  				},
    86  			}
    87  		case "/apis":
    88  			obj = &metav1.APIGroupList{
    89  				Groups: []metav1.APIGroup{
    90  					{
    91  						Name: "extensions",
    92  						Versions: []metav1.GroupVersionForDiscovery{
    93  							{GroupVersion: "extensions/v1beta1"},
    94  						},
    95  					},
    96  				},
    97  			}
    98  		default:
    99  			w.WriteHeader(http.StatusNotFound)
   100  			return
   101  		}
   102  		output, err := json.Marshal(obj)
   103  		if err != nil {
   104  			t.Fatalf("unexpected encoding error: %v", err)
   105  			return
   106  		}
   107  		w.Header().Set("Content-Type", "application/json")
   108  		w.WriteHeader(http.StatusOK)
   109  		_, err = w.Write(output)
   110  		require.NoError(t, err)
   111  	}))
   112  	defer server.Close()
   113  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   114  	apiGroupList, err := client.ServerGroups()
   115  	if err != nil {
   116  		t.Fatalf("unexpected error: %v", err)
   117  	}
   118  	groupVersions := metav1.ExtractGroupVersions(apiGroupList)
   119  	if !reflect.DeepEqual(groupVersions, []string{"v1", "extensions/v1beta1"}) {
   120  		t.Errorf("expected: %q, got: %q", []string{"v1", "extensions/v1beta1"}, groupVersions)
   121  	}
   122  }
   123  
   124  func TestDiscoveryToleratesMissingCoreGroup(t *testing.T) {
   125  	// Discovery tolerates 404 from /api. Aggregated api servers can do this.
   126  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   127  		var obj interface{}
   128  		switch req.URL.Path {
   129  		case "/api":
   130  			w.WriteHeader(http.StatusNotFound)
   131  		case "/apis":
   132  			obj = &metav1.APIGroupList{
   133  				Groups: []metav1.APIGroup{
   134  					{
   135  						Name: "extensions",
   136  						Versions: []metav1.GroupVersionForDiscovery{
   137  							{GroupVersion: "extensions/v1beta1"},
   138  						},
   139  					},
   140  				},
   141  			}
   142  		}
   143  		output, err := json.Marshal(obj)
   144  		if err != nil {
   145  			t.Fatalf("unexpected encoding error: %v", err)
   146  			return
   147  		}
   148  		w.Header().Set("Content-Type", "application/json")
   149  		w.WriteHeader(http.StatusOK)
   150  		_, err = w.Write(output)
   151  		require.NoError(t, err)
   152  	}))
   153  	defer server.Close()
   154  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   155  	// ServerGroups should not return an error even if server returns 404 at /api.
   156  	apiGroupList, err := client.ServerGroups()
   157  	if err != nil {
   158  		t.Fatalf("unexpected error: %v", err)
   159  	}
   160  	groupVersions := metav1.ExtractGroupVersions(apiGroupList)
   161  	if !reflect.DeepEqual(groupVersions, []string{"extensions/v1beta1"}) {
   162  		t.Errorf("expected: %q, got: %q", []string{"extensions/v1beta1"}, groupVersions)
   163  	}
   164  }
   165  
   166  func TestDiscoveryFailsWhenNonCoreGroupsMissing(t *testing.T) {
   167  	// Discovery fails when /apis returns 404.
   168  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   169  		var obj interface{}
   170  		switch req.URL.Path {
   171  		case "/api":
   172  			obj = &metav1.APIVersions{
   173  				Versions: []string{
   174  					"v1",
   175  				},
   176  			}
   177  		case "/apis":
   178  			w.WriteHeader(http.StatusNotFound)
   179  		}
   180  		output, err := json.Marshal(obj)
   181  		if err != nil {
   182  			t.Fatalf("unexpected encoding error: %v", err)
   183  			return
   184  		}
   185  		w.Header().Set("Content-Type", "application/json")
   186  		w.WriteHeader(http.StatusOK)
   187  		_, err = w.Write(output)
   188  		require.NoError(t, err)
   189  	}))
   190  	defer server.Close()
   191  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   192  	_, err := client.ServerGroups()
   193  	if err == nil {
   194  		t.Fatal("expected error, received none")
   195  	}
   196  }
   197  
   198  func TestGetServerGroupsWithBrokenServer(t *testing.T) {
   199  	// 404 Not Found errors because discovery at /apis returns an error.
   200  	// 403 Forbidden errors because discovery at both /api and /apis returns error.
   201  	for _, statusCode := range []int{http.StatusNotFound, http.StatusForbidden} {
   202  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   203  			w.WriteHeader(statusCode)
   204  		}))
   205  		defer server.Close()
   206  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   207  		_, err := client.ServerGroups()
   208  		if err == nil {
   209  			t.Fatal("expected error, received none")
   210  		}
   211  	}
   212  }
   213  
   214  func TestTimeoutIsSet(t *testing.T) {
   215  	cfg := &restclient.Config{}
   216  	setDiscoveryDefaults(cfg)
   217  	assert.Equal(t, defaultTimeout, cfg.Timeout)
   218  }
   219  
   220  func TestGetServerResourcesForGroupVersion(t *testing.T) {
   221  	stable := metav1.APIResourceList{
   222  		GroupVersion: "v1",
   223  		APIResources: []metav1.APIResource{
   224  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   225  			{Name: "services", Namespaced: true, Kind: "Service"},
   226  			{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
   227  		},
   228  	}
   229  	beta := metav1.APIResourceList{
   230  		GroupVersion: "extensions/v1beta1",
   231  		APIResources: []metav1.APIResource{
   232  			{Name: "deployments", Namespaced: true, Kind: "Deployment"},
   233  			{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
   234  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   235  		},
   236  	}
   237  	beta2 := metav1.APIResourceList{
   238  		GroupVersion: "extensions/v1beta2",
   239  		APIResources: []metav1.APIResource{
   240  			{Name: "deployments", Namespaced: true, Kind: "Deployment"},
   241  			{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
   242  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   243  		},
   244  	}
   245  	extensionsbeta3 := metav1.APIResourceList{GroupVersion: "extensions/v1beta3", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   246  	extensionsbeta4 := metav1.APIResourceList{GroupVersion: "extensions/v1beta4", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   247  	extensionsbeta5 := metav1.APIResourceList{GroupVersion: "extensions/v1beta5", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   248  	extensionsbeta6 := metav1.APIResourceList{GroupVersion: "extensions/v1beta6", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   249  	extensionsbeta7 := metav1.APIResourceList{GroupVersion: "extensions/v1beta7", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   250  	extensionsbeta8 := metav1.APIResourceList{GroupVersion: "extensions/v1beta8", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   251  	extensionsbeta9 := metav1.APIResourceList{GroupVersion: "extensions/v1beta9", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   252  	extensionsbeta10 := metav1.APIResourceList{GroupVersion: "extensions/v1beta10", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   253  
   254  	appsbeta1 := metav1.APIResourceList{GroupVersion: "apps/v1beta1", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   255  	appsbeta2 := metav1.APIResourceList{GroupVersion: "apps/v1beta2", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   256  	appsbeta3 := metav1.APIResourceList{GroupVersion: "apps/v1beta3", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   257  	appsbeta4 := metav1.APIResourceList{GroupVersion: "apps/v1beta4", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   258  	appsbeta5 := metav1.APIResourceList{GroupVersion: "apps/v1beta5", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   259  	appsbeta6 := metav1.APIResourceList{GroupVersion: "apps/v1beta6", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   260  	appsbeta7 := metav1.APIResourceList{GroupVersion: "apps/v1beta7", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   261  	appsbeta8 := metav1.APIResourceList{GroupVersion: "apps/v1beta8", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   262  	appsbeta9 := metav1.APIResourceList{GroupVersion: "apps/v1beta9", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   263  	appsbeta10 := metav1.APIResourceList{GroupVersion: "apps/v1beta10", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   264  
   265  	tests := []struct {
   266  		resourcesList *metav1.APIResourceList
   267  		path          string
   268  		request       string
   269  		expectErr     bool
   270  	}{
   271  		{
   272  			resourcesList: &stable,
   273  			path:          "/api/v1",
   274  			request:       "v1",
   275  			expectErr:     false,
   276  		},
   277  		{
   278  			resourcesList: &beta,
   279  			path:          "/apis/extensions/v1beta1",
   280  			request:       "extensions/v1beta1",
   281  			expectErr:     false,
   282  		},
   283  		{
   284  			resourcesList: &stable,
   285  			path:          "/api/v1",
   286  			request:       "foobar",
   287  			expectErr:     true,
   288  		},
   289  	}
   290  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   291  		var list interface{}
   292  		switch req.URL.Path {
   293  		case "/api/v1":
   294  			list = &stable
   295  		case "/apis/extensions/v1beta1":
   296  			list = &beta
   297  		case "/apis/extensions/v1beta2":
   298  			list = &beta2
   299  		case "/apis/extensions/v1beta3":
   300  			list = &extensionsbeta3
   301  		case "/apis/extensions/v1beta4":
   302  			list = &extensionsbeta4
   303  		case "/apis/extensions/v1beta5":
   304  			list = &extensionsbeta5
   305  		case "/apis/extensions/v1beta6":
   306  			list = &extensionsbeta6
   307  		case "/apis/extensions/v1beta7":
   308  			list = &extensionsbeta7
   309  		case "/apis/extensions/v1beta8":
   310  			list = &extensionsbeta8
   311  		case "/apis/extensions/v1beta9":
   312  			list = &extensionsbeta9
   313  		case "/apis/extensions/v1beta10":
   314  			list = &extensionsbeta10
   315  		case "/apis/apps/v1beta1":
   316  			list = &appsbeta1
   317  		case "/apis/apps/v1beta2":
   318  			list = &appsbeta2
   319  		case "/apis/apps/v1beta3":
   320  			list = &appsbeta3
   321  		case "/apis/apps/v1beta4":
   322  			list = &appsbeta4
   323  		case "/apis/apps/v1beta5":
   324  			list = &appsbeta5
   325  		case "/apis/apps/v1beta6":
   326  			list = &appsbeta6
   327  		case "/apis/apps/v1beta7":
   328  			list = &appsbeta7
   329  		case "/apis/apps/v1beta8":
   330  			list = &appsbeta8
   331  		case "/apis/apps/v1beta9":
   332  			list = &appsbeta9
   333  		case "/apis/apps/v1beta10":
   334  			list = &appsbeta10
   335  		case "/api":
   336  			list = &metav1.APIVersions{
   337  				Versions: []string{
   338  					"v1",
   339  				},
   340  			}
   341  		case "/apis":
   342  			list = &metav1.APIGroupList{
   343  				Groups: []metav1.APIGroup{
   344  					{
   345  						Name: "apps",
   346  						Versions: []metav1.GroupVersionForDiscovery{
   347  							{GroupVersion: "apps/v1beta1", Version: "v1beta1"},
   348  							{GroupVersion: "apps/v1beta2", Version: "v1beta2"},
   349  							{GroupVersion: "apps/v1beta3", Version: "v1beta3"},
   350  							{GroupVersion: "apps/v1beta4", Version: "v1beta4"},
   351  							{GroupVersion: "apps/v1beta5", Version: "v1beta5"},
   352  							{GroupVersion: "apps/v1beta6", Version: "v1beta6"},
   353  							{GroupVersion: "apps/v1beta7", Version: "v1beta7"},
   354  							{GroupVersion: "apps/v1beta8", Version: "v1beta8"},
   355  							{GroupVersion: "apps/v1beta9", Version: "v1beta9"},
   356  							{GroupVersion: "apps/v1beta10", Version: "v1beta10"},
   357  						},
   358  					},
   359  					{
   360  						Name: "extensions",
   361  						Versions: []metav1.GroupVersionForDiscovery{
   362  							{GroupVersion: "extensions/v1beta1", Version: "v1beta1"},
   363  							{GroupVersion: "extensions/v1beta2", Version: "v1beta2"},
   364  							{GroupVersion: "extensions/v1beta3", Version: "v1beta3"},
   365  							{GroupVersion: "extensions/v1beta4", Version: "v1beta4"},
   366  							{GroupVersion: "extensions/v1beta5", Version: "v1beta5"},
   367  							{GroupVersion: "extensions/v1beta6", Version: "v1beta6"},
   368  							{GroupVersion: "extensions/v1beta7", Version: "v1beta7"},
   369  							{GroupVersion: "extensions/v1beta8", Version: "v1beta8"},
   370  							{GroupVersion: "extensions/v1beta9", Version: "v1beta9"},
   371  							{GroupVersion: "extensions/v1beta10", Version: "v1beta10"},
   372  						},
   373  					},
   374  				},
   375  			}
   376  		default:
   377  			t.Logf("unexpected request: %s", req.URL.Path)
   378  			w.WriteHeader(http.StatusNotFound)
   379  			return
   380  		}
   381  		output, err := json.Marshal(list)
   382  		if err != nil {
   383  			t.Errorf("unexpected encoding error: %v", err)
   384  			return
   385  		}
   386  		w.Header().Set("Content-Type", "application/json")
   387  		w.WriteHeader(http.StatusOK)
   388  		_, err = w.Write(output)
   389  		require.NoError(t, err)
   390  	}))
   391  	defer server.Close()
   392  	for _, test := range tests {
   393  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   394  		got, err := client.ServerResourcesForGroupVersion(test.request)
   395  		if test.expectErr {
   396  			if err == nil {
   397  				t.Error("unexpected non-error")
   398  			}
   399  			continue
   400  		}
   401  		if err != nil {
   402  			t.Errorf("unexpected error: %v", err)
   403  			continue
   404  		}
   405  		if !reflect.DeepEqual(got, test.resourcesList) {
   406  			t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got)
   407  		}
   408  	}
   409  
   410  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   411  	start := time.Now()
   412  	_, serverResources, err := client.ServerGroupsAndResources()
   413  	if err != nil {
   414  		t.Errorf("unexpected error: %v", err)
   415  	}
   416  	end := time.Now()
   417  	if d := end.Sub(start); d > time.Second {
   418  		t.Errorf("took too long to perform discovery: %s", d)
   419  	}
   420  	serverGroupVersions := groupVersions(serverResources)
   421  	expectedGroupVersions := []string{
   422  		"v1",
   423  		"apps/v1beta1",
   424  		"apps/v1beta2",
   425  		"apps/v1beta3",
   426  		"apps/v1beta4",
   427  		"apps/v1beta5",
   428  		"apps/v1beta6",
   429  		"apps/v1beta7",
   430  		"apps/v1beta8",
   431  		"apps/v1beta9",
   432  		"apps/v1beta10",
   433  		"extensions/v1beta1",
   434  		"extensions/v1beta2",
   435  		"extensions/v1beta3",
   436  		"extensions/v1beta4",
   437  		"extensions/v1beta5",
   438  		"extensions/v1beta6",
   439  		"extensions/v1beta7",
   440  		"extensions/v1beta8",
   441  		"extensions/v1beta9",
   442  		"extensions/v1beta10",
   443  	}
   444  	if !reflect.DeepEqual(expectedGroupVersions, serverGroupVersions) {
   445  		t.Errorf("unexpected group versions: %v", cmp.Diff(expectedGroupVersions, serverGroupVersions))
   446  	}
   447  }
   448  
   449  func returnedOpenAPI() *openapi_v2.Document {
   450  	return &openapi_v2.Document{
   451  		Definitions: &openapi_v2.Definitions{
   452  			AdditionalProperties: []*openapi_v2.NamedSchema{
   453  				{
   454  					Name: "fake.type.1",
   455  					Value: &openapi_v2.Schema{
   456  						Properties: &openapi_v2.Properties{
   457  							AdditionalProperties: []*openapi_v2.NamedSchema{
   458  								{
   459  									Name: "count",
   460  									Value: &openapi_v2.Schema{
   461  										Type: &openapi_v2.TypeItem{
   462  											Value: []string{"integer"},
   463  										},
   464  									},
   465  								},
   466  							},
   467  						},
   468  					},
   469  				},
   470  				{
   471  					Name: "fake.type.2",
   472  					Value: &openapi_v2.Schema{
   473  						Properties: &openapi_v2.Properties{
   474  							AdditionalProperties: []*openapi_v2.NamedSchema{
   475  								{
   476  									Name: "count",
   477  									Value: &openapi_v2.Schema{
   478  										Type: &openapi_v2.TypeItem{
   479  											Value: []string{"array"},
   480  										},
   481  										Items: &openapi_v2.ItemsItem{
   482  											Schema: []*openapi_v2.Schema{
   483  												{
   484  													Type: &openapi_v2.TypeItem{
   485  														Value: []string{"string"},
   486  													},
   487  												},
   488  											},
   489  										},
   490  									},
   491  								},
   492  							},
   493  						},
   494  					},
   495  				},
   496  			},
   497  		},
   498  	}
   499  }
   500  
   501  func openapiSchemaFakeServer(t *testing.T) (*httptest.Server, error) {
   502  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   503  		if req.URL.Path != "/openapi/v2" {
   504  			errMsg := fmt.Sprintf("Unexpected url %v", req.URL)
   505  			w.WriteHeader(http.StatusNotFound)
   506  			w.Write([]byte(errMsg))
   507  			t.Errorf("testing should fail as %s", errMsg)
   508  			return
   509  		}
   510  		if req.Method != "GET" {
   511  			errMsg := fmt.Sprintf("Unexpected method %v", req.Method)
   512  			w.WriteHeader(http.StatusMethodNotAllowed)
   513  			w.Write([]byte(errMsg))
   514  			t.Errorf("testing should fail as %s", errMsg)
   515  			return
   516  		}
   517  		decipherableFormat := req.Header.Get("Accept")
   518  		if decipherableFormat != "application/com.github.proto-openapi.spec.v2@v1.0+protobuf" {
   519  			errMsg := fmt.Sprintf("Unexpected accept mime type %v", decipherableFormat)
   520  			w.WriteHeader(http.StatusUnsupportedMediaType)
   521  			w.Write([]byte(errMsg))
   522  			t.Errorf("testing should fail as %s", errMsg)
   523  			return
   524  		}
   525  
   526  		output, err := proto.Marshal(returnedOpenAPI())
   527  		if err != nil {
   528  			errMsg := fmt.Sprintf("Unexpected marshal error: %v", err)
   529  			w.WriteHeader(http.StatusInternalServerError)
   530  			w.Write([]byte(errMsg))
   531  			t.Errorf("testing should fail as %s", errMsg)
   532  			return
   533  		}
   534  		w.WriteHeader(http.StatusOK)
   535  		w.Write(output)
   536  	}))
   537  
   538  	return server, nil
   539  }
   540  
   541  func openapiV3SchemaFakeServer(t *testing.T) (*httptest.Server, map[string]*spec3.OpenAPI, error) {
   542  	res, err := testutil.NewFakeOpenAPIV3Server("testdata")
   543  	if err != nil {
   544  		return nil, nil, err
   545  	}
   546  	return res.HttpServer, res.ServedDocuments, nil
   547  }
   548  
   549  func TestGetOpenAPISchema(t *testing.T) {
   550  	server, err := openapiSchemaFakeServer(t)
   551  	if err != nil {
   552  		t.Errorf("unexpected error starting fake server: %v", err)
   553  	}
   554  	defer server.Close()
   555  
   556  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   557  	got, err := client.OpenAPISchema()
   558  	if err != nil {
   559  		t.Fatalf("unexpected error getting openapi: %v", err)
   560  	}
   561  	if e, a := returnedOpenAPI(), got; !golangproto.Equal(e, a) {
   562  		t.Errorf("expected \n%v, got \n%v", e, a)
   563  	}
   564  }
   565  
   566  func TestGetOpenAPISchemaV3(t *testing.T) {
   567  	server, testV3Specs, err := openapiV3SchemaFakeServer(t)
   568  	if err != nil {
   569  		t.Errorf("unexpected error starting fake server: %v", err)
   570  	}
   571  	defer server.Close()
   572  
   573  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   574  	openapiClient := client.OpenAPIV3()
   575  	paths, err := openapiClient.Paths()
   576  	if err != nil {
   577  		t.Fatalf("unexpected error getting openapi: %v", err)
   578  	}
   579  
   580  	contentTypes := []string{
   581  		runtime.ContentTypeJSON, openapi.ContentTypeOpenAPIV3PB,
   582  	}
   583  
   584  	for _, contentType := range contentTypes {
   585  		t.Run(contentType, func(t *testing.T) {
   586  			for k, v := range paths {
   587  				actual, err := v.Schema(contentType)
   588  				if err != nil {
   589  					t.Fatal(err)
   590  				}
   591  
   592  				expected := testV3Specs[k]
   593  				switch contentType {
   594  
   595  				case runtime.ContentTypeJSON:
   596  					var actualSpec spec3.OpenAPI
   597  
   598  					if err := json.Unmarshal(actual, &actualSpec); err != nil {
   599  						t.Fatal(err)
   600  					}
   601  
   602  					// Cannot use DeepEqual directly due to differences in how
   603  					// default key is being handled in gnostic vs kube-openapi
   604  					// Our test server parses the files in directly as gnostic
   605  					// which retains empty maps/lists, etc.
   606  					require.EqualValues(t, expected, &actualSpec)
   607  				case openapi.ContentTypeOpenAPIV3PB:
   608  					// Convert to JSON then to gnostic then to PB for comparison
   609  					expectedJSON, err := json.Marshal(expected)
   610  					if err != nil {
   611  						t.Fatal(err)
   612  					}
   613  
   614  					expectedGnostic, err := openapi_v3.ParseDocument(expectedJSON)
   615  					if err != nil {
   616  						t.Fatal(err)
   617  					}
   618  
   619  					expectedPB, err := golangproto.Marshal(expectedGnostic)
   620  					if err != nil {
   621  						t.Fatal(err)
   622  					}
   623  					if !reflect.DeepEqual(expectedPB, actual) {
   624  						t.Fatalf("expected equal values: %v", cmp.Diff(expectedPB, actual))
   625  					}
   626  				default:
   627  					panic(fmt.Errorf("unrecognized content type: %v", contentType))
   628  				}
   629  
   630  				// Ensure that fetching schema once again does not return same instance
   631  				actualAgain, err := v.Schema(contentType)
   632  				if err != nil {
   633  					t.Fatal(err)
   634  				}
   635  
   636  				if reflect.ValueOf(actual).Pointer() == reflect.ValueOf(actualAgain).Pointer() {
   637  					t.Fatal("expected schema not to be cached")
   638  				}
   639  			}
   640  
   641  		})
   642  	}
   643  }
   644  
   645  func TestServerPreferredResources(t *testing.T) {
   646  	stable := metav1.APIResourceList{
   647  		GroupVersion: "v1",
   648  		APIResources: []metav1.APIResource{
   649  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   650  			{Name: "services", Namespaced: true, Kind: "Service"},
   651  			{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
   652  		},
   653  	}
   654  	tests := []struct {
   655  		resourcesList []*metav1.APIResourceList
   656  		response      func(w http.ResponseWriter, req *http.Request)
   657  		expectErr     func(err error) bool
   658  	}{
   659  		{
   660  			resourcesList: []*metav1.APIResourceList{&stable},
   661  			expectErr:     IsGroupDiscoveryFailedError,
   662  			response: func(w http.ResponseWriter, req *http.Request) {
   663  				var list interface{}
   664  				switch req.URL.Path {
   665  				case "/apis/extensions/v1beta1":
   666  					w.WriteHeader(http.StatusInternalServerError)
   667  					return
   668  				case "/api/v1":
   669  					list = &stable
   670  				case "/api":
   671  					list = &metav1.APIVersions{
   672  						Versions: []string{
   673  							"v1",
   674  						},
   675  					}
   676  				case "/apis":
   677  					list = &metav1.APIGroupList{
   678  						Groups: []metav1.APIGroup{
   679  							{
   680  								Versions: []metav1.GroupVersionForDiscovery{
   681  									{GroupVersion: "extensions/v1beta1"},
   682  								},
   683  							},
   684  						},
   685  					}
   686  				default:
   687  					t.Logf("unexpected request: %s", req.URL.Path)
   688  					w.WriteHeader(http.StatusNotFound)
   689  					return
   690  				}
   691  				output, err := json.Marshal(list)
   692  				if err != nil {
   693  					t.Errorf("unexpected encoding error: %v", err)
   694  					return
   695  				}
   696  				w.Header().Set("Content-Type", "application/json")
   697  				w.WriteHeader(http.StatusOK)
   698  				w.Write(output)
   699  			},
   700  		},
   701  		{
   702  			resourcesList: nil,
   703  			expectErr:     IsGroupDiscoveryFailedError,
   704  			response: func(w http.ResponseWriter, req *http.Request) {
   705  				var list interface{}
   706  				switch req.URL.Path {
   707  				case "/apis/extensions/v1beta1":
   708  					w.WriteHeader(http.StatusInternalServerError)
   709  					return
   710  				case "/api/v1":
   711  					w.WriteHeader(http.StatusInternalServerError)
   712  				case "/api":
   713  					list = &metav1.APIVersions{
   714  						Versions: []string{
   715  							"v1",
   716  						},
   717  					}
   718  				case "/apis":
   719  					list = &metav1.APIGroupList{
   720  						Groups: []metav1.APIGroup{
   721  							{
   722  								Versions: []metav1.GroupVersionForDiscovery{
   723  									{GroupVersion: "extensions/v1beta1"},
   724  								},
   725  							},
   726  						},
   727  					}
   728  				default:
   729  					t.Logf("unexpected request: %s", req.URL.Path)
   730  					w.WriteHeader(http.StatusNotFound)
   731  					return
   732  				}
   733  				output, err := json.Marshal(list)
   734  				if err != nil {
   735  					t.Errorf("unexpected encoding error: %v", err)
   736  					return
   737  				}
   738  				w.Header().Set("Content-Type", "application/json")
   739  				w.WriteHeader(http.StatusOK)
   740  				w.Write(output)
   741  			},
   742  		},
   743  	}
   744  	for _, test := range tests {
   745  		server := httptest.NewServer(http.HandlerFunc(test.response))
   746  		defer server.Close()
   747  
   748  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   749  		resources, err := client.ServerPreferredResources()
   750  		if test.expectErr != nil {
   751  			if err == nil {
   752  				t.Error("unexpected non-error")
   753  			}
   754  
   755  			continue
   756  		}
   757  		if err != nil {
   758  			t.Errorf("unexpected error: %v", err)
   759  			continue
   760  		}
   761  		got, err := GroupVersionResources(resources)
   762  		if err != nil {
   763  			t.Errorf("unexpected error: %v", err)
   764  			continue
   765  		}
   766  		expected, _ := GroupVersionResources(test.resourcesList)
   767  		if !reflect.DeepEqual(got, expected) {
   768  			t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got)
   769  		}
   770  		server.Close()
   771  	}
   772  }
   773  
   774  func TestServerPreferredResourcesRetries(t *testing.T) {
   775  	stable := metav1.APIResourceList{
   776  		GroupVersion: "v1",
   777  		APIResources: []metav1.APIResource{
   778  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   779  		},
   780  	}
   781  	beta := metav1.APIResourceList{
   782  		GroupVersion: "extensions/v1",
   783  		APIResources: []metav1.APIResource{
   784  			{Name: "deployments", Namespaced: true, Kind: "Deployment"},
   785  		},
   786  	}
   787  
   788  	response := func(numErrors int) http.HandlerFunc {
   789  		var i = 0
   790  		return func(w http.ResponseWriter, req *http.Request) {
   791  			var list interface{}
   792  			switch req.URL.Path {
   793  			case "/apis/extensions/v1beta1":
   794  				if i < numErrors {
   795  					i++
   796  					w.WriteHeader(http.StatusInternalServerError)
   797  					return
   798  				}
   799  				list = &beta
   800  			case "/api/v1":
   801  				list = &stable
   802  			case "/api":
   803  				list = &metav1.APIVersions{
   804  					Versions: []string{
   805  						"v1",
   806  					},
   807  				}
   808  			case "/apis":
   809  				list = &metav1.APIGroupList{
   810  					Groups: []metav1.APIGroup{
   811  						{
   812  							Name: "extensions",
   813  							Versions: []metav1.GroupVersionForDiscovery{
   814  								{GroupVersion: "extensions/v1beta1", Version: "v1beta1"},
   815  							},
   816  							PreferredVersion: metav1.GroupVersionForDiscovery{
   817  								GroupVersion: "extensions/v1beta1",
   818  								Version:      "v1beta1",
   819  							},
   820  						},
   821  					},
   822  				}
   823  			default:
   824  				t.Logf("unexpected request: %s", req.URL.Path)
   825  				w.WriteHeader(http.StatusNotFound)
   826  				return
   827  			}
   828  			output, err := json.Marshal(list)
   829  			if err != nil {
   830  				t.Errorf("unexpected encoding error: %v", err)
   831  				return
   832  			}
   833  			w.Header().Set("Content-Type", "application/json")
   834  			w.WriteHeader(http.StatusOK)
   835  			w.Write(output)
   836  		}
   837  	}
   838  	tests := []struct {
   839  		responseErrors  int
   840  		expectResources int
   841  		expectedError   func(err error) bool
   842  	}{
   843  		{
   844  			responseErrors:  1,
   845  			expectResources: 2,
   846  			expectedError: func(err error) bool {
   847  				return err == nil
   848  			},
   849  		},
   850  		{
   851  			responseErrors:  2,
   852  			expectResources: 1,
   853  			expectedError:   IsGroupDiscoveryFailedError,
   854  		},
   855  	}
   856  
   857  	for i, tc := range tests {
   858  		server := httptest.NewServer(http.HandlerFunc(response(tc.responseErrors)))
   859  		defer server.Close()
   860  
   861  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   862  		resources, err := client.ServerPreferredResources()
   863  		if !tc.expectedError(err) {
   864  			t.Errorf("case %d: unexpected error: %v", i, err)
   865  		}
   866  		got, err := GroupVersionResources(resources)
   867  		if err != nil {
   868  			t.Errorf("case %d: unexpected error: %v", i, err)
   869  		}
   870  		if len(got) != tc.expectResources {
   871  			t.Errorf("case %d: expect %d resources, got %#v", i, tc.expectResources, got)
   872  		}
   873  		server.Close()
   874  	}
   875  }
   876  
   877  func TestServerPreferredNamespacedResources(t *testing.T) {
   878  	stable := metav1.APIResourceList{
   879  		GroupVersion: "v1",
   880  		APIResources: []metav1.APIResource{
   881  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   882  			{Name: "services", Namespaced: true, Kind: "Service"},
   883  			{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
   884  		},
   885  	}
   886  	batchv1 := metav1.APIResourceList{
   887  		GroupVersion: "batch/v1",
   888  		APIResources: []metav1.APIResource{
   889  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   890  		},
   891  	}
   892  	batchv2alpha1 := metav1.APIResourceList{
   893  		GroupVersion: "batch/v2alpha1",
   894  		APIResources: []metav1.APIResource{
   895  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   896  			{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
   897  		},
   898  	}
   899  	batchv3alpha1 := metav1.APIResourceList{
   900  		GroupVersion: "batch/v3alpha1",
   901  		APIResources: []metav1.APIResource{
   902  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   903  			{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
   904  		},
   905  	}
   906  	tests := []struct {
   907  		response func(w http.ResponseWriter, req *http.Request)
   908  		expected map[schema.GroupVersionResource]struct{}
   909  	}{
   910  		{
   911  			// Combines discovery for /api and /apis.
   912  			response: func(w http.ResponseWriter, req *http.Request) {
   913  				var list interface{}
   914  				switch req.URL.Path {
   915  				case "/api":
   916  					list = &metav1.APIVersions{
   917  						Versions: []string{
   918  							"v1",
   919  						},
   920  					}
   921  				case "/api/v1":
   922  					list = &stable
   923  				case "/apis":
   924  					list = &metav1.APIGroupList{
   925  						Groups: []metav1.APIGroup{
   926  							{
   927  								Name: "batch",
   928  								Versions: []metav1.GroupVersionForDiscovery{
   929  									{GroupVersion: "batch/v1", Version: "v1"},
   930  								},
   931  								PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v1", Version: "v1"},
   932  							},
   933  						},
   934  					}
   935  				case "/apis/batch/v1":
   936  					list = &batchv1
   937  
   938  				default:
   939  					t.Logf("unexpected request: %s", req.URL.Path)
   940  					w.WriteHeader(http.StatusNotFound)
   941  					return
   942  				}
   943  				output, err := json.Marshal(list)
   944  				if err != nil {
   945  					t.Errorf("unexpected encoding error: %v", err)
   946  					return
   947  				}
   948  				w.Header().Set("Content-Type", "application/json")
   949  				w.WriteHeader(http.StatusOK)
   950  				w.Write(output)
   951  			},
   952  			expected: map[schema.GroupVersionResource]struct{}{
   953  				{Group: "", Version: "v1", Resource: "pods"}:      {},
   954  				{Group: "", Version: "v1", Resource: "services"}:  {},
   955  				{Group: "batch", Version: "v1", Resource: "jobs"}: {},
   956  			},
   957  		},
   958  		{
   959  			// Only return /apis (not legacy /api); does not error. 404 for legacy
   960  			// core/v1 at /api is tolerated.
   961  			response: func(w http.ResponseWriter, req *http.Request) {
   962  				var list interface{}
   963  				switch req.URL.Path {
   964  				case "/apis":
   965  					list = &metav1.APIGroupList{
   966  						Groups: []metav1.APIGroup{
   967  							{
   968  								Name: "batch",
   969  								Versions: []metav1.GroupVersionForDiscovery{
   970  									{GroupVersion: "batch/v1", Version: "v1"},
   971  									{GroupVersion: "batch/v2alpha1", Version: "v2alpha1"},
   972  									{GroupVersion: "batch/v3alpha1", Version: "v3alpha1"},
   973  								},
   974  								PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v1", Version: "v1"},
   975  							},
   976  						},
   977  					}
   978  				case "/apis/batch/v1":
   979  					list = &batchv1
   980  				case "/apis/batch/v2alpha1":
   981  					list = &batchv2alpha1
   982  				case "/apis/batch/v3alpha1":
   983  					list = &batchv3alpha1
   984  				default:
   985  					t.Logf("unexpected request: %s", req.URL.Path)
   986  					w.WriteHeader(http.StatusNotFound)
   987  					return
   988  				}
   989  				output, err := json.Marshal(list)
   990  				if err != nil {
   991  					t.Errorf("unexpected encoding error: %v", err)
   992  					return
   993  				}
   994  				w.Header().Set("Content-Type", "application/json")
   995  				w.WriteHeader(http.StatusOK)
   996  				w.Write(output)
   997  			},
   998  			expected: map[schema.GroupVersionResource]struct{}{
   999  				{Group: "batch", Version: "v1", Resource: "jobs"}:           {},
  1000  				{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
  1001  			},
  1002  		},
  1003  		{
  1004  			response: func(w http.ResponseWriter, req *http.Request) {
  1005  				var list interface{}
  1006  				switch req.URL.Path {
  1007  				case "/apis":
  1008  					list = &metav1.APIGroupList{
  1009  						Groups: []metav1.APIGroup{
  1010  							{
  1011  								Name: "batch",
  1012  								Versions: []metav1.GroupVersionForDiscovery{
  1013  									{GroupVersion: "batch/v1", Version: "v1"},
  1014  									{GroupVersion: "batch/v2alpha1", Version: "v2alpha1"},
  1015  									{GroupVersion: "batch/v3alpha1", Version: "v3alpha1"},
  1016  								},
  1017  								PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v2alpha", Version: "v2alpha1"},
  1018  							},
  1019  						},
  1020  					}
  1021  				case "/apis/batch/v1":
  1022  					list = &batchv1
  1023  				case "/apis/batch/v2alpha1":
  1024  					list = &batchv2alpha1
  1025  				case "/apis/batch/v3alpha1":
  1026  					list = &batchv3alpha1
  1027  				default:
  1028  					t.Logf("unexpected request: %s", req.URL.Path)
  1029  					w.WriteHeader(http.StatusNotFound)
  1030  					return
  1031  				}
  1032  				output, err := json.Marshal(list)
  1033  				if err != nil {
  1034  					t.Errorf("unexpected encoding error: %v", err)
  1035  					return
  1036  				}
  1037  				w.Header().Set("Content-Type", "application/json")
  1038  				w.WriteHeader(http.StatusOK)
  1039  				w.Write(output)
  1040  			},
  1041  			expected: map[schema.GroupVersionResource]struct{}{
  1042  				{Group: "batch", Version: "v2alpha1", Resource: "jobs"}:     {},
  1043  				{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
  1044  			},
  1045  		},
  1046  	}
  1047  	for i, test := range tests {
  1048  		server := httptest.NewServer(http.HandlerFunc(test.response))
  1049  		defer server.Close()
  1050  
  1051  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  1052  		resources, err := client.ServerPreferredNamespacedResources()
  1053  		if err != nil {
  1054  			t.Errorf("[%d] unexpected error: %v", i, err)
  1055  			continue
  1056  		}
  1057  		got, err := GroupVersionResources(resources)
  1058  		if err != nil {
  1059  			t.Errorf("[%d] unexpected error: %v", i, err)
  1060  			continue
  1061  		}
  1062  
  1063  		if !reflect.DeepEqual(got, test.expected) {
  1064  			t.Errorf("[%d] expected:\n%v\ngot:\n%v\n", i, test.expected, got)
  1065  		}
  1066  		server.Close()
  1067  	}
  1068  }
  1069  
  1070  // Tests of the aggregated discovery format.
  1071  func TestAggregatedServerGroups(t *testing.T) {
  1072  	tests := []struct {
  1073  		name                      string
  1074  		corev1                    *apidiscovery.APIGroupDiscoveryList
  1075  		apis                      *apidiscovery.APIGroupDiscoveryList
  1076  		expectedGroupNames        []string
  1077  		expectedGroupVersions     []string
  1078  		expectedPreferredVersions []string
  1079  	}{
  1080  		{
  1081  			name: "Aggregated discovery: 1 group/1 version at /api, 1 group/1 version at /apis",
  1082  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1083  				Items: []apidiscovery.APIGroupDiscovery{
  1084  					{
  1085  						Versions: []apidiscovery.APIVersionDiscovery{
  1086  							{
  1087  								Version: "v1",
  1088  								Resources: []apidiscovery.APIResourceDiscovery{
  1089  									{
  1090  										Resource: "pods",
  1091  										ResponseKind: &metav1.GroupVersionKind{
  1092  											Group:   "",
  1093  											Version: "v1",
  1094  											Kind:    "Pod",
  1095  										},
  1096  										Scope: apidiscovery.ScopeNamespace,
  1097  									},
  1098  								},
  1099  							},
  1100  						},
  1101  					},
  1102  				},
  1103  			},
  1104  			apis: &apidiscovery.APIGroupDiscoveryList{
  1105  				Items: []apidiscovery.APIGroupDiscovery{
  1106  					{
  1107  						ObjectMeta: metav1.ObjectMeta{
  1108  							Name: "apps",
  1109  						},
  1110  						Versions: []apidiscovery.APIVersionDiscovery{
  1111  							{
  1112  								Version: "v1",
  1113  								Resources: []apidiscovery.APIResourceDiscovery{
  1114  									{
  1115  										Resource: "deployments",
  1116  										ResponseKind: &metav1.GroupVersionKind{
  1117  											Group:   "apps",
  1118  											Version: "v1",
  1119  											Kind:    "Deployment",
  1120  										},
  1121  										Scope: apidiscovery.ScopeNamespace,
  1122  									},
  1123  								},
  1124  							},
  1125  						},
  1126  					},
  1127  				},
  1128  			},
  1129  			expectedGroupNames:        []string{"", "apps"},
  1130  			expectedGroupVersions:     []string{"v1", "apps/v1"},
  1131  			expectedPreferredVersions: []string{"v1", "apps/v1"},
  1132  		},
  1133  		{
  1134  			name: "Aggregated discovery: 1 group/1 version at /api, 1 group/2 versions at /apis",
  1135  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1136  				Items: []apidiscovery.APIGroupDiscovery{
  1137  					{
  1138  						ObjectMeta: metav1.ObjectMeta{
  1139  							Name: "",
  1140  						},
  1141  						Versions: []apidiscovery.APIVersionDiscovery{
  1142  							{
  1143  								Version: "v1",
  1144  								Resources: []apidiscovery.APIResourceDiscovery{
  1145  									{
  1146  										Resource: "pods",
  1147  										ResponseKind: &metav1.GroupVersionKind{
  1148  											Group:   "",
  1149  											Version: "v1",
  1150  											Kind:    "Pod",
  1151  										},
  1152  										Scope: apidiscovery.ScopeNamespace,
  1153  									},
  1154  								},
  1155  							},
  1156  						},
  1157  					},
  1158  				},
  1159  			},
  1160  			apis: &apidiscovery.APIGroupDiscoveryList{
  1161  				Items: []apidiscovery.APIGroupDiscovery{
  1162  					{
  1163  						ObjectMeta: metav1.ObjectMeta{
  1164  							Name: "apps",
  1165  						},
  1166  						Versions: []apidiscovery.APIVersionDiscovery{
  1167  							// v2 is preferred since it is first
  1168  							{
  1169  								Version: "v2",
  1170  								Resources: []apidiscovery.APIResourceDiscovery{
  1171  									{
  1172  										Resource: "deployments",
  1173  										ResponseKind: &metav1.GroupVersionKind{
  1174  											Group:   "apps",
  1175  											Version: "v2",
  1176  											Kind:    "Deployment",
  1177  										},
  1178  										Scope: apidiscovery.ScopeNamespace,
  1179  									},
  1180  								},
  1181  							},
  1182  							{
  1183  								Version: "v1",
  1184  								Resources: []apidiscovery.APIResourceDiscovery{
  1185  									{
  1186  										Resource: "deployments",
  1187  										ResponseKind: &metav1.GroupVersionKind{
  1188  											Group:   "apps",
  1189  											Version: "v1",
  1190  											Kind:    "Deployment",
  1191  										},
  1192  										Scope: apidiscovery.ScopeNamespace,
  1193  									},
  1194  								},
  1195  							},
  1196  						},
  1197  					},
  1198  				},
  1199  			},
  1200  			expectedGroupNames:        []string{"", "apps"},
  1201  			expectedGroupVersions:     []string{"v1", "apps/v1", "apps/v2"},
  1202  			expectedPreferredVersions: []string{"v1", "apps/v2"},
  1203  		},
  1204  		{
  1205  			name:   "Aggregated discovery: /api returns nothing, 2 groups at /apis",
  1206  			corev1: &apidiscovery.APIGroupDiscoveryList{},
  1207  			apis: &apidiscovery.APIGroupDiscoveryList{
  1208  				Items: []apidiscovery.APIGroupDiscovery{
  1209  					{
  1210  						ObjectMeta: metav1.ObjectMeta{
  1211  							Name: "apps",
  1212  						},
  1213  						Versions: []apidiscovery.APIVersionDiscovery{
  1214  							{
  1215  								Version: "v1",
  1216  								Resources: []apidiscovery.APIResourceDiscovery{
  1217  									{
  1218  										Resource: "deployments",
  1219  										ResponseKind: &metav1.GroupVersionKind{
  1220  											Group:   "apps",
  1221  											Version: "v1",
  1222  											Kind:    "Deployment",
  1223  										},
  1224  										Scope: apidiscovery.ScopeNamespace,
  1225  									},
  1226  									{
  1227  										Resource: "statefulsets",
  1228  										ResponseKind: &metav1.GroupVersionKind{
  1229  											Group:   "apps",
  1230  											Version: "v1",
  1231  											Kind:    "StatefulSet",
  1232  										},
  1233  										Scope: apidiscovery.ScopeNamespace,
  1234  									},
  1235  								},
  1236  							},
  1237  						},
  1238  					},
  1239  					{
  1240  						ObjectMeta: metav1.ObjectMeta{
  1241  							Name: "batch",
  1242  						},
  1243  						Versions: []apidiscovery.APIVersionDiscovery{
  1244  							// v1 is preferred since it is first
  1245  							{
  1246  								Version: "v1",
  1247  								Resources: []apidiscovery.APIResourceDiscovery{
  1248  									{
  1249  										Resource: "jobs",
  1250  										ResponseKind: &metav1.GroupVersionKind{
  1251  											Group:   "batch",
  1252  											Version: "v1",
  1253  											Kind:    "Job",
  1254  										},
  1255  										Scope: apidiscovery.ScopeNamespace,
  1256  									},
  1257  									{
  1258  										Resource: "cronjobs",
  1259  										ResponseKind: &metav1.GroupVersionKind{
  1260  											Group:   "batch",
  1261  											Version: "v1",
  1262  											Kind:    "CronJob",
  1263  										},
  1264  										Scope: apidiscovery.ScopeNamespace,
  1265  									},
  1266  								},
  1267  							},
  1268  							{
  1269  								Version: "v1beta1",
  1270  								Resources: []apidiscovery.APIResourceDiscovery{
  1271  									{
  1272  										Resource: "jobs",
  1273  										ResponseKind: &metav1.GroupVersionKind{
  1274  											Group:   "batch",
  1275  											Version: "v1beta1",
  1276  											Kind:    "Job",
  1277  										},
  1278  										Scope: apidiscovery.ScopeNamespace,
  1279  									},
  1280  									{
  1281  										Resource: "cronjobs",
  1282  										ResponseKind: &metav1.GroupVersionKind{
  1283  											Group:   "batch",
  1284  											Version: "v1beta1",
  1285  											Kind:    "CronJob",
  1286  										},
  1287  										Scope: apidiscovery.ScopeNamespace,
  1288  									},
  1289  								},
  1290  							},
  1291  						},
  1292  					},
  1293  				},
  1294  			},
  1295  			expectedGroupNames:        []string{"apps", "batch"},
  1296  			expectedGroupVersions:     []string{"apps/v1", "batch/v1", "batch/v1beta1"},
  1297  			expectedPreferredVersions: []string{"apps/v1", "batch/v1"},
  1298  		},
  1299  	}
  1300  
  1301  	for _, test := range tests {
  1302  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1303  			var output []byte
  1304  			var err error
  1305  			var agg *apidiscovery.APIGroupDiscoveryList
  1306  			switch req.URL.Path {
  1307  			case "/api":
  1308  				agg = test.corev1
  1309  			case "/apis":
  1310  				agg = test.apis
  1311  			default:
  1312  				w.WriteHeader(http.StatusNotFound)
  1313  				return
  1314  			}
  1315  			output, err = json.Marshal(agg)
  1316  			require.NoError(t, err)
  1317  			// Content-Type is "aggregated" discovery format. Add extra parameter
  1318  			// to ensure we are resilient to these extra parameters.
  1319  			w.Header().Set("Content-Type", AcceptV2+"; charset=utf-8")
  1320  			w.WriteHeader(http.StatusOK)
  1321  			_, err = w.Write(output)
  1322  			require.NoError(t, err)
  1323  		}))
  1324  		defer server.Close()
  1325  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  1326  		apiGroupList, err := client.ServerGroups()
  1327  		require.NoError(t, err)
  1328  		// Test the expected groups are returned for the aggregated format.
  1329  		expectedGroupNames := sets.NewString(test.expectedGroupNames...)
  1330  		actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...)
  1331  		assert.True(t, expectedGroupNames.Equal(actualGroupNames),
  1332  			"%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
  1333  		// Test the expected group versions for the aggregated discovery is correct.
  1334  		expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
  1335  		actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...)
  1336  		assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
  1337  			"%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
  1338  		// Test the groups preferred version is correct.
  1339  		expectedPreferredVersions := sets.NewString(test.expectedPreferredVersions...)
  1340  		actualPreferredVersions := sets.NewString(preferredVersionsFromList(apiGroupList)...)
  1341  		assert.True(t, expectedPreferredVersions.Equal(actualPreferredVersions),
  1342  			"%s: Expected preferred group/version (%s), got (%s)", test.name, expectedPreferredVersions.List(), actualPreferredVersions.List())
  1343  	}
  1344  }
  1345  
  1346  func TestAggregatedServerGroupsAndResources(t *testing.T) {
  1347  	tests := []struct {
  1348  		name                  string
  1349  		corev1                *apidiscovery.APIGroupDiscoveryList
  1350  		corev1DiscoveryBeta   *apidiscoveryv2beta1.APIGroupDiscoveryList
  1351  		apis                  *apidiscovery.APIGroupDiscoveryList
  1352  		apisDiscoveryBeta     *apidiscoveryv2beta1.APIGroupDiscoveryList
  1353  		expectedGroupNames    []string
  1354  		expectedGroupVersions []string
  1355  		expectedGVKs          []string
  1356  		expectedFailedGVs     []string
  1357  	}{
  1358  		{
  1359  			name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/1 resources at /apis",
  1360  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1361  				Items: []apidiscovery.APIGroupDiscovery{
  1362  					{
  1363  						Versions: []apidiscovery.APIVersionDiscovery{
  1364  							{
  1365  								Version: "v1",
  1366  								Resources: []apidiscovery.APIResourceDiscovery{
  1367  									{
  1368  										Resource: "pods",
  1369  										ResponseKind: &metav1.GroupVersionKind{
  1370  											Group:   "",
  1371  											Version: "v1",
  1372  											Kind:    "Pod",
  1373  										},
  1374  										Scope: apidiscovery.ScopeNamespace,
  1375  									},
  1376  								},
  1377  							},
  1378  						},
  1379  					},
  1380  				},
  1381  			},
  1382  			corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1383  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1384  					{
  1385  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1386  							{
  1387  								Version: "v1",
  1388  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1389  									{
  1390  										Resource: "pods",
  1391  										ResponseKind: &metav1.GroupVersionKind{
  1392  											Group:   "",
  1393  											Version: "v1",
  1394  											Kind:    "Pod",
  1395  										},
  1396  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1397  									},
  1398  								},
  1399  							},
  1400  						},
  1401  					},
  1402  				},
  1403  			},
  1404  			apis: &apidiscovery.APIGroupDiscoveryList{
  1405  				Items: []apidiscovery.APIGroupDiscovery{
  1406  					{
  1407  						ObjectMeta: metav1.ObjectMeta{
  1408  							Name: "apps",
  1409  						},
  1410  						Versions: []apidiscovery.APIVersionDiscovery{
  1411  							{
  1412  								Version: "v1",
  1413  								Resources: []apidiscovery.APIResourceDiscovery{
  1414  									{
  1415  										Resource: "deployments",
  1416  										ResponseKind: &metav1.GroupVersionKind{
  1417  											Group:   "apps",
  1418  											Version: "v1",
  1419  											Kind:    "Deployment",
  1420  										},
  1421  										Scope: apidiscovery.ScopeNamespace,
  1422  									},
  1423  								},
  1424  							},
  1425  						},
  1426  					},
  1427  				},
  1428  			},
  1429  			apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1430  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1431  					{
  1432  						ObjectMeta: metav1.ObjectMeta{
  1433  							Name: "apps",
  1434  						},
  1435  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1436  							{
  1437  								Version: "v1",
  1438  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1439  									{
  1440  										Resource: "deployments",
  1441  										ResponseKind: &metav1.GroupVersionKind{
  1442  											Group:   "apps",
  1443  											Version: "v1",
  1444  											Kind:    "Deployment",
  1445  										},
  1446  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1447  									},
  1448  								},
  1449  							},
  1450  						},
  1451  					},
  1452  				},
  1453  			},
  1454  			expectedGroupNames:    []string{"", "apps"},
  1455  			expectedGroupVersions: []string{"v1", "apps/v1"},
  1456  			expectedGVKs: []string{
  1457  				"/v1/Pod",
  1458  				"apps/v1/Deployment",
  1459  			},
  1460  		},
  1461  		{
  1462  			name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis",
  1463  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1464  				Items: []apidiscovery.APIGroupDiscovery{
  1465  					{
  1466  						Versions: []apidiscovery.APIVersionDiscovery{
  1467  							{
  1468  								Version: "v1",
  1469  								Resources: []apidiscovery.APIResourceDiscovery{
  1470  									{
  1471  										Resource: "pods",
  1472  										ResponseKind: &metav1.GroupVersionKind{
  1473  											Group:   "",
  1474  											Version: "v1",
  1475  											Kind:    "Pod",
  1476  										},
  1477  										Scope: apidiscovery.ScopeNamespace,
  1478  									},
  1479  								},
  1480  							},
  1481  						},
  1482  					},
  1483  				},
  1484  			},
  1485  			corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1486  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1487  					{
  1488  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1489  							{
  1490  								Version: "v1",
  1491  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1492  									{
  1493  										Resource: "pods",
  1494  										ResponseKind: &metav1.GroupVersionKind{
  1495  											Group:   "",
  1496  											Version: "v1",
  1497  											Kind:    "Pod",
  1498  										},
  1499  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1500  									},
  1501  								},
  1502  							},
  1503  						},
  1504  					},
  1505  				},
  1506  			},
  1507  			apis: &apidiscovery.APIGroupDiscoveryList{
  1508  				Items: []apidiscovery.APIGroupDiscovery{
  1509  					{
  1510  						ObjectMeta: metav1.ObjectMeta{
  1511  							Name: "apps",
  1512  						},
  1513  						Versions: []apidiscovery.APIVersionDiscovery{
  1514  							{
  1515  								Version: "v1",
  1516  								Resources: []apidiscovery.APIResourceDiscovery{
  1517  									{
  1518  										Resource: "deployments",
  1519  										ResponseKind: &metav1.GroupVersionKind{
  1520  											Group:   "apps",
  1521  											Version: "v1",
  1522  											Kind:    "Deployment",
  1523  										},
  1524  										Scope: apidiscovery.ScopeNamespace,
  1525  									},
  1526  								},
  1527  							},
  1528  							{
  1529  								Version: "v2",
  1530  								Resources: []apidiscovery.APIResourceDiscovery{
  1531  									{
  1532  										Resource: "deployments",
  1533  										ResponseKind: &metav1.GroupVersionKind{
  1534  											Group:   "apps",
  1535  											Version: "v2",
  1536  											Kind:    "Deployment",
  1537  										},
  1538  										Scope: apidiscovery.ScopeNamespace,
  1539  									},
  1540  								},
  1541  							},
  1542  						},
  1543  					},
  1544  				},
  1545  			},
  1546  			apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1547  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1548  					{
  1549  						ObjectMeta: metav1.ObjectMeta{
  1550  							Name: "apps",
  1551  						},
  1552  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1553  							{
  1554  								Version: "v1",
  1555  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1556  									{
  1557  										Resource: "deployments",
  1558  										ResponseKind: &metav1.GroupVersionKind{
  1559  											Group:   "apps",
  1560  											Version: "v1",
  1561  											Kind:    "Deployment",
  1562  										},
  1563  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1564  									},
  1565  								},
  1566  							},
  1567  							{
  1568  								Version: "v2",
  1569  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1570  									{
  1571  										Resource: "deployments",
  1572  										ResponseKind: &metav1.GroupVersionKind{
  1573  											Group:   "apps",
  1574  											Version: "v2",
  1575  											Kind:    "Deployment",
  1576  										},
  1577  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1578  									},
  1579  								},
  1580  							},
  1581  						},
  1582  					},
  1583  				},
  1584  			},
  1585  			expectedGroupNames:    []string{"", "apps"},
  1586  			expectedGroupVersions: []string{"v1", "apps/v1", "apps/v2"},
  1587  			expectedGVKs: []string{
  1588  				"/v1/Pod",
  1589  				"apps/v1/Deployment",
  1590  				"apps/v2/Deployment",
  1591  			},
  1592  		},
  1593  		{
  1594  			name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis",
  1595  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1596  				Items: []apidiscovery.APIGroupDiscovery{
  1597  					{
  1598  						Versions: []apidiscovery.APIVersionDiscovery{
  1599  							{
  1600  								Version: "v1",
  1601  								Resources: []apidiscovery.APIResourceDiscovery{
  1602  									{
  1603  										Resource: "pods",
  1604  										ResponseKind: &metav1.GroupVersionKind{
  1605  											Group:   "",
  1606  											Version: "v1",
  1607  											Kind:    "Pod",
  1608  										},
  1609  										Scope: apidiscovery.ScopeNamespace,
  1610  									},
  1611  								},
  1612  							},
  1613  						},
  1614  					},
  1615  				},
  1616  			},
  1617  			corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1618  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1619  					{
  1620  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1621  							{
  1622  								Version: "v1",
  1623  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1624  									{
  1625  										Resource: "pods",
  1626  										ResponseKind: &metav1.GroupVersionKind{
  1627  											Group:   "",
  1628  											Version: "v1",
  1629  											Kind:    "Pod",
  1630  										},
  1631  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1632  									},
  1633  								},
  1634  							},
  1635  						},
  1636  					},
  1637  				},
  1638  			},
  1639  			apis: &apidiscovery.APIGroupDiscoveryList{
  1640  				Items: []apidiscovery.APIGroupDiscovery{
  1641  					{
  1642  						ObjectMeta: metav1.ObjectMeta{
  1643  							Name: "apps",
  1644  						},
  1645  						Versions: []apidiscovery.APIVersionDiscovery{
  1646  							{
  1647  								Version: "v1",
  1648  								Resources: []apidiscovery.APIResourceDiscovery{
  1649  									{
  1650  										Resource: "deployments",
  1651  										ResponseKind: &metav1.GroupVersionKind{
  1652  											Group:   "apps",
  1653  											Version: "v1",
  1654  											Kind:    "Deployment",
  1655  										},
  1656  										Scope: apidiscovery.ScopeNamespace,
  1657  									},
  1658  								},
  1659  							},
  1660  							{
  1661  								Version: "v2",
  1662  								Resources: []apidiscovery.APIResourceDiscovery{
  1663  									{
  1664  										Resource: "deployments",
  1665  										ResponseKind: &metav1.GroupVersionKind{
  1666  											Group:   "apps",
  1667  											Version: "v2",
  1668  											Kind:    "Deployment",
  1669  										},
  1670  										Scope: apidiscovery.ScopeNamespace,
  1671  									},
  1672  								},
  1673  								Freshness: apidiscovery.DiscoveryFreshnessStale,
  1674  							},
  1675  						},
  1676  					},
  1677  				},
  1678  			},
  1679  			apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1680  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1681  					{
  1682  						ObjectMeta: metav1.ObjectMeta{
  1683  							Name: "apps",
  1684  						},
  1685  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1686  							{
  1687  								Version: "v1",
  1688  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1689  									{
  1690  										Resource: "deployments",
  1691  										ResponseKind: &metav1.GroupVersionKind{
  1692  											Group:   "apps",
  1693  											Version: "v1",
  1694  											Kind:    "Deployment",
  1695  										},
  1696  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1697  									},
  1698  								},
  1699  							},
  1700  							{
  1701  								Version: "v2",
  1702  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1703  									{
  1704  										Resource: "deployments",
  1705  										ResponseKind: &metav1.GroupVersionKind{
  1706  											Group:   "apps",
  1707  											Version: "v2",
  1708  											Kind:    "Deployment",
  1709  										},
  1710  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1711  									},
  1712  								},
  1713  								Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
  1714  							},
  1715  						},
  1716  					},
  1717  				},
  1718  			},
  1719  			expectedGroupNames:    []string{"", "apps"},
  1720  			expectedGroupVersions: []string{"v1", "apps/v1"},
  1721  			expectedGVKs: []string{
  1722  				"/v1/Pod",
  1723  				"apps/v1/Deployment",
  1724  			},
  1725  			expectedFailedGVs: []string{"apps/v2"},
  1726  		},
  1727  		{
  1728  			name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis",
  1729  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1730  				Items: []apidiscovery.APIGroupDiscovery{
  1731  					{
  1732  						Versions: []apidiscovery.APIVersionDiscovery{
  1733  							{
  1734  								Version: "v1",
  1735  								Resources: []apidiscovery.APIResourceDiscovery{
  1736  									{
  1737  										Resource: "pods",
  1738  										ResponseKind: &metav1.GroupVersionKind{
  1739  											Group:   "",
  1740  											Version: "v1",
  1741  											Kind:    "Pod",
  1742  										},
  1743  										Scope: apidiscovery.ScopeNamespace,
  1744  									},
  1745  									{
  1746  										Resource: "services",
  1747  										ResponseKind: &metav1.GroupVersionKind{
  1748  											Group:   "",
  1749  											Version: "v1",
  1750  											Kind:    "Service",
  1751  										},
  1752  										Scope: apidiscovery.ScopeNamespace,
  1753  									},
  1754  								},
  1755  							},
  1756  						},
  1757  					},
  1758  				},
  1759  			},
  1760  			corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1761  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1762  					{
  1763  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1764  							{
  1765  								Version: "v1",
  1766  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1767  									{
  1768  										Resource: "pods",
  1769  										ResponseKind: &metav1.GroupVersionKind{
  1770  											Group:   "",
  1771  											Version: "v1",
  1772  											Kind:    "Pod",
  1773  										},
  1774  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1775  									},
  1776  									{
  1777  										Resource: "services",
  1778  										ResponseKind: &metav1.GroupVersionKind{
  1779  											Group:   "",
  1780  											Version: "v1",
  1781  											Kind:    "Service",
  1782  										},
  1783  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1784  									},
  1785  								},
  1786  							},
  1787  						},
  1788  					},
  1789  				},
  1790  			},
  1791  			apis: &apidiscovery.APIGroupDiscoveryList{
  1792  				Items: []apidiscovery.APIGroupDiscovery{
  1793  					{
  1794  						ObjectMeta: metav1.ObjectMeta{
  1795  							Name: "apps",
  1796  						},
  1797  						Versions: []apidiscovery.APIVersionDiscovery{
  1798  							// Stale "v2" version not included.
  1799  							{
  1800  								Version: "v2",
  1801  								Resources: []apidiscovery.APIResourceDiscovery{
  1802  									{
  1803  										Resource: "deployments",
  1804  										ResponseKind: &metav1.GroupVersionKind{
  1805  											Group:   "apps",
  1806  											Version: "v2",
  1807  											Kind:    "Deployment",
  1808  										},
  1809  										Scope: apidiscovery.ScopeNamespace,
  1810  									},
  1811  									{
  1812  										Resource: "statefulsets",
  1813  										ResponseKind: &metav1.GroupVersionKind{
  1814  											Group:   "apps",
  1815  											Version: "v2",
  1816  											Kind:    "StatefulSet",
  1817  										},
  1818  										Scope: apidiscovery.ScopeNamespace,
  1819  									},
  1820  								},
  1821  								Freshness: apidiscovery.DiscoveryFreshnessStale,
  1822  							},
  1823  							{
  1824  								Version: "v1",
  1825  								Resources: []apidiscovery.APIResourceDiscovery{
  1826  									{
  1827  										Resource: "deployments",
  1828  										ResponseKind: &metav1.GroupVersionKind{
  1829  											Group:   "apps",
  1830  											Version: "v1",
  1831  											Kind:    "Deployment",
  1832  										},
  1833  										Scope: apidiscovery.ScopeNamespace,
  1834  									},
  1835  									{
  1836  										Resource: "statefulsets",
  1837  										ResponseKind: &metav1.GroupVersionKind{
  1838  											Group:   "apps",
  1839  											Version: "v1",
  1840  											Kind:    "StatefulSet",
  1841  										},
  1842  										Scope: apidiscovery.ScopeNamespace,
  1843  									},
  1844  								},
  1845  							},
  1846  						},
  1847  					},
  1848  				},
  1849  			},
  1850  			apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1851  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1852  					{
  1853  						ObjectMeta: metav1.ObjectMeta{
  1854  							Name: "apps",
  1855  						},
  1856  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1857  							// Stale "v2" version not included.
  1858  							{
  1859  								Version: "v2",
  1860  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1861  									{
  1862  										Resource: "deployments",
  1863  										ResponseKind: &metav1.GroupVersionKind{
  1864  											Group:   "apps",
  1865  											Version: "v2",
  1866  											Kind:    "Deployment",
  1867  										},
  1868  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1869  									},
  1870  									{
  1871  										Resource: "statefulsets",
  1872  										ResponseKind: &metav1.GroupVersionKind{
  1873  											Group:   "apps",
  1874  											Version: "v2",
  1875  											Kind:    "StatefulSet",
  1876  										},
  1877  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1878  									},
  1879  								},
  1880  								Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
  1881  							},
  1882  							{
  1883  								Version: "v1",
  1884  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1885  									{
  1886  										Resource: "deployments",
  1887  										ResponseKind: &metav1.GroupVersionKind{
  1888  											Group:   "apps",
  1889  											Version: "v1",
  1890  											Kind:    "Deployment",
  1891  										},
  1892  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1893  									},
  1894  									{
  1895  										Resource: "statefulsets",
  1896  										ResponseKind: &metav1.GroupVersionKind{
  1897  											Group:   "apps",
  1898  											Version: "v1",
  1899  											Kind:    "StatefulSet",
  1900  										},
  1901  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1902  									},
  1903  								},
  1904  							},
  1905  						},
  1906  					},
  1907  				},
  1908  			},
  1909  			expectedGroupNames:    []string{"", "apps"},
  1910  			expectedGroupVersions: []string{"v1", "apps/v1"},
  1911  			expectedGVKs: []string{
  1912  				"/v1/Pod",
  1913  				"/v1/Service",
  1914  				"apps/v1/Deployment",
  1915  				"apps/v1/StatefulSet",
  1916  			},
  1917  			expectedFailedGVs: []string{"apps/v2"},
  1918  		},
  1919  		{
  1920  			name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources/1 stale GV at /apis",
  1921  			corev1: &apidiscovery.APIGroupDiscoveryList{
  1922  				Items: []apidiscovery.APIGroupDiscovery{
  1923  					{
  1924  						Versions: []apidiscovery.APIVersionDiscovery{
  1925  							{
  1926  								Version: "v1",
  1927  								Resources: []apidiscovery.APIResourceDiscovery{
  1928  									{
  1929  										Resource: "pods",
  1930  										ResponseKind: &metav1.GroupVersionKind{
  1931  											Group:   "",
  1932  											Version: "v1",
  1933  											Kind:    "Pod",
  1934  										},
  1935  										Scope: apidiscovery.ScopeNamespace,
  1936  									},
  1937  									{
  1938  										Resource: "services",
  1939  										ResponseKind: &metav1.GroupVersionKind{
  1940  											Group:   "",
  1941  											Version: "v1",
  1942  											Kind:    "Service",
  1943  										},
  1944  										Scope: apidiscovery.ScopeNamespace,
  1945  									},
  1946  								},
  1947  							},
  1948  						},
  1949  					},
  1950  				},
  1951  			},
  1952  			corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  1953  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  1954  					{
  1955  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  1956  							{
  1957  								Version: "v1",
  1958  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  1959  									{
  1960  										Resource: "pods",
  1961  										ResponseKind: &metav1.GroupVersionKind{
  1962  											Group:   "",
  1963  											Version: "v1",
  1964  											Kind:    "Pod",
  1965  										},
  1966  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1967  									},
  1968  									{
  1969  										Resource: "services",
  1970  										ResponseKind: &metav1.GroupVersionKind{
  1971  											Group:   "",
  1972  											Version: "v1",
  1973  											Kind:    "Service",
  1974  										},
  1975  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  1976  									},
  1977  								},
  1978  							},
  1979  						},
  1980  					},
  1981  				},
  1982  			},
  1983  			apis: &apidiscovery.APIGroupDiscoveryList{
  1984  				Items: []apidiscovery.APIGroupDiscovery{
  1985  					{
  1986  						ObjectMeta: metav1.ObjectMeta{
  1987  							Name: "apps",
  1988  						},
  1989  						Versions: []apidiscovery.APIVersionDiscovery{
  1990  							{
  1991  								Version: "v1",
  1992  								Resources: []apidiscovery.APIResourceDiscovery{
  1993  									{
  1994  										Resource: "deployments",
  1995  										ResponseKind: &metav1.GroupVersionKind{
  1996  											Group:   "apps",
  1997  											Version: "v1",
  1998  											Kind:    "Deployment",
  1999  										},
  2000  										Scope: apidiscovery.ScopeNamespace,
  2001  									},
  2002  									{
  2003  										Resource: "statefulsets",
  2004  										ResponseKind: &metav1.GroupVersionKind{
  2005  											Group:   "apps",
  2006  											Version: "v1",
  2007  											Kind:    "StatefulSet",
  2008  										},
  2009  										Scope: apidiscovery.ScopeNamespace,
  2010  									},
  2011  								},
  2012  							},
  2013  						},
  2014  					},
  2015  					{
  2016  						ObjectMeta: metav1.ObjectMeta{
  2017  							Name: "batch",
  2018  						},
  2019  						Versions: []apidiscovery.APIVersionDiscovery{
  2020  							// Stale Group/Version is not included
  2021  							{
  2022  								Version: "v1",
  2023  								Resources: []apidiscovery.APIResourceDiscovery{
  2024  									{
  2025  										Resource: "jobs",
  2026  										ResponseKind: &metav1.GroupVersionKind{
  2027  											Group:   "batch",
  2028  											Version: "v1",
  2029  											Kind:    "Job",
  2030  										},
  2031  										Scope: apidiscovery.ScopeNamespace,
  2032  									},
  2033  									{
  2034  										Resource: "cronjobs",
  2035  										ResponseKind: &metav1.GroupVersionKind{
  2036  											Group:   "batch",
  2037  											Version: "v1",
  2038  											Kind:    "CronJob",
  2039  										},
  2040  										Scope: apidiscovery.ScopeNamespace,
  2041  									},
  2042  								},
  2043  								Freshness: apidiscovery.DiscoveryFreshnessStale,
  2044  							},
  2045  							{
  2046  								Version: "v1beta1",
  2047  								Resources: []apidiscovery.APIResourceDiscovery{
  2048  									{
  2049  										Resource: "jobs",
  2050  										ResponseKind: &metav1.GroupVersionKind{
  2051  											Group:   "batch",
  2052  											Version: "v1beta1",
  2053  											Kind:    "Job",
  2054  										},
  2055  										Scope: apidiscovery.ScopeNamespace,
  2056  									},
  2057  									{
  2058  										Resource: "cronjobs",
  2059  										ResponseKind: &metav1.GroupVersionKind{
  2060  											Group:   "batch",
  2061  											Version: "v1beta1",
  2062  											Kind:    "CronJob",
  2063  										},
  2064  										Scope: apidiscovery.ScopeNamespace,
  2065  									},
  2066  								},
  2067  							},
  2068  						},
  2069  					},
  2070  				},
  2071  			},
  2072  			apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  2073  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  2074  					{
  2075  						ObjectMeta: metav1.ObjectMeta{
  2076  							Name: "apps",
  2077  						},
  2078  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  2079  							{
  2080  								Version: "v1",
  2081  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  2082  									{
  2083  										Resource: "deployments",
  2084  										ResponseKind: &metav1.GroupVersionKind{
  2085  											Group:   "apps",
  2086  											Version: "v1",
  2087  											Kind:    "Deployment",
  2088  										},
  2089  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2090  									},
  2091  									{
  2092  										Resource: "statefulsets",
  2093  										ResponseKind: &metav1.GroupVersionKind{
  2094  											Group:   "apps",
  2095  											Version: "v1",
  2096  											Kind:    "StatefulSet",
  2097  										},
  2098  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2099  									},
  2100  								},
  2101  							},
  2102  						},
  2103  					},
  2104  					{
  2105  						ObjectMeta: metav1.ObjectMeta{
  2106  							Name: "batch",
  2107  						},
  2108  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  2109  							// Stale Group/Version is not included
  2110  							{
  2111  								Version: "v1",
  2112  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  2113  									{
  2114  										Resource: "jobs",
  2115  										ResponseKind: &metav1.GroupVersionKind{
  2116  											Group:   "batch",
  2117  											Version: "v1",
  2118  											Kind:    "Job",
  2119  										},
  2120  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2121  									},
  2122  									{
  2123  										Resource: "cronjobs",
  2124  										ResponseKind: &metav1.GroupVersionKind{
  2125  											Group:   "batch",
  2126  											Version: "v1",
  2127  											Kind:    "CronJob",
  2128  										},
  2129  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2130  									},
  2131  								},
  2132  								Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
  2133  							},
  2134  							{
  2135  								Version: "v1beta1",
  2136  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  2137  									{
  2138  										Resource: "jobs",
  2139  										ResponseKind: &metav1.GroupVersionKind{
  2140  											Group:   "batch",
  2141  											Version: "v1beta1",
  2142  											Kind:    "Job",
  2143  										},
  2144  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2145  									},
  2146  									{
  2147  										Resource: "cronjobs",
  2148  										ResponseKind: &metav1.GroupVersionKind{
  2149  											Group:   "batch",
  2150  											Version: "v1beta1",
  2151  											Kind:    "CronJob",
  2152  										},
  2153  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2154  									},
  2155  								},
  2156  							},
  2157  						},
  2158  					},
  2159  				},
  2160  			},
  2161  			expectedGroupNames:    []string{"", "apps", "batch"},
  2162  			expectedGroupVersions: []string{"v1", "apps/v1", "batch/v1beta1"},
  2163  			expectedGVKs: []string{
  2164  				"/v1/Pod",
  2165  				"/v1/Service",
  2166  				"apps/v1/Deployment",
  2167  				"apps/v1/StatefulSet",
  2168  				"batch/v1beta1/Job",
  2169  				"batch/v1beta1/CronJob",
  2170  			},
  2171  			expectedFailedGVs: []string{"batch/v1"},
  2172  		},
  2173  		{
  2174  			name:                "Aggregated discovery: /api returns nothing, 2 groups/2 resources at /apis",
  2175  			corev1:              &apidiscovery.APIGroupDiscoveryList{},
  2176  			corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{},
  2177  			apis: &apidiscovery.APIGroupDiscoveryList{
  2178  				Items: []apidiscovery.APIGroupDiscovery{
  2179  					{
  2180  						ObjectMeta: metav1.ObjectMeta{
  2181  							Name: "apps",
  2182  						},
  2183  						Versions: []apidiscovery.APIVersionDiscovery{
  2184  							{
  2185  								Version: "v1",
  2186  								Resources: []apidiscovery.APIResourceDiscovery{
  2187  									{
  2188  										Resource: "deployments",
  2189  										ResponseKind: &metav1.GroupVersionKind{
  2190  											Group:   "apps",
  2191  											Version: "v1",
  2192  											Kind:    "Deployment",
  2193  										},
  2194  										Scope: apidiscovery.ScopeNamespace,
  2195  									},
  2196  									{
  2197  										Resource: "statefulsets",
  2198  										ResponseKind: &metav1.GroupVersionKind{
  2199  											Group:   "apps",
  2200  											Version: "v1",
  2201  											Kind:    "StatefulSet",
  2202  										},
  2203  										Scope: apidiscovery.ScopeNamespace,
  2204  									},
  2205  								},
  2206  							},
  2207  						},
  2208  					},
  2209  					{
  2210  						ObjectMeta: metav1.ObjectMeta{
  2211  							Name: "batch",
  2212  						},
  2213  						Versions: []apidiscovery.APIVersionDiscovery{
  2214  							{
  2215  								Version: "v1",
  2216  								Resources: []apidiscovery.APIResourceDiscovery{
  2217  									{
  2218  										Resource: "jobs",
  2219  										ResponseKind: &metav1.GroupVersionKind{
  2220  											Group:   "batch",
  2221  											Version: "v1",
  2222  											Kind:    "Job",
  2223  										},
  2224  										Scope: apidiscovery.ScopeNamespace,
  2225  									},
  2226  									{
  2227  										Resource: "cronjobs",
  2228  										ResponseKind: &metav1.GroupVersionKind{
  2229  											Group:   "batch",
  2230  											Version: "v1",
  2231  											Kind:    "CronJob",
  2232  										},
  2233  										Scope: apidiscovery.ScopeNamespace,
  2234  									},
  2235  								},
  2236  							},
  2237  							{
  2238  								// Stale "v1beta1" not included.
  2239  								Version: "v1beta1",
  2240  								Resources: []apidiscovery.APIResourceDiscovery{
  2241  									{
  2242  										Resource: "jobs",
  2243  										ResponseKind: &metav1.GroupVersionKind{
  2244  											Group:   "batch",
  2245  											Version: "v1beta1",
  2246  											Kind:    "Job",
  2247  										},
  2248  										Scope: apidiscovery.ScopeNamespace,
  2249  									},
  2250  									{
  2251  										Resource: "cronjobs",
  2252  										ResponseKind: &metav1.GroupVersionKind{
  2253  											Group:   "batch",
  2254  											Version: "v1beta1",
  2255  											Kind:    "CronJob",
  2256  										},
  2257  										Scope: apidiscovery.ScopeNamespace,
  2258  									},
  2259  								},
  2260  								Freshness: apidiscovery.DiscoveryFreshnessStale,
  2261  							},
  2262  						},
  2263  					},
  2264  				},
  2265  			},
  2266  			apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
  2267  				Items: []apidiscoveryv2beta1.APIGroupDiscovery{
  2268  					{
  2269  						ObjectMeta: metav1.ObjectMeta{
  2270  							Name: "apps",
  2271  						},
  2272  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  2273  							{
  2274  								Version: "v1",
  2275  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  2276  									{
  2277  										Resource: "deployments",
  2278  										ResponseKind: &metav1.GroupVersionKind{
  2279  											Group:   "apps",
  2280  											Version: "v1",
  2281  											Kind:    "Deployment",
  2282  										},
  2283  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2284  									},
  2285  									{
  2286  										Resource: "statefulsets",
  2287  										ResponseKind: &metav1.GroupVersionKind{
  2288  											Group:   "apps",
  2289  											Version: "v1",
  2290  											Kind:    "StatefulSet",
  2291  										},
  2292  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2293  									},
  2294  								},
  2295  							},
  2296  						},
  2297  					},
  2298  					{
  2299  						ObjectMeta: metav1.ObjectMeta{
  2300  							Name: "batch",
  2301  						},
  2302  						Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
  2303  							{
  2304  								Version: "v1",
  2305  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  2306  									{
  2307  										Resource: "jobs",
  2308  										ResponseKind: &metav1.GroupVersionKind{
  2309  											Group:   "batch",
  2310  											Version: "v1",
  2311  											Kind:    "Job",
  2312  										},
  2313  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2314  									},
  2315  									{
  2316  										Resource: "cronjobs",
  2317  										ResponseKind: &metav1.GroupVersionKind{
  2318  											Group:   "batch",
  2319  											Version: "v1",
  2320  											Kind:    "CronJob",
  2321  										},
  2322  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2323  									},
  2324  								},
  2325  							},
  2326  							{
  2327  								// Stale "v1beta1" not included.
  2328  								Version: "v1beta1",
  2329  								Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
  2330  									{
  2331  										Resource: "jobs",
  2332  										ResponseKind: &metav1.GroupVersionKind{
  2333  											Group:   "batch",
  2334  											Version: "v1beta1",
  2335  											Kind:    "Job",
  2336  										},
  2337  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2338  									},
  2339  									{
  2340  										Resource: "cronjobs",
  2341  										ResponseKind: &metav1.GroupVersionKind{
  2342  											Group:   "batch",
  2343  											Version: "v1beta1",
  2344  											Kind:    "CronJob",
  2345  										},
  2346  										Scope: apidiscoveryv2beta1.ScopeNamespace,
  2347  									},
  2348  								},
  2349  								Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
  2350  							},
  2351  						},
  2352  					},
  2353  				},
  2354  			},
  2355  			expectedGroupNames:    []string{"apps", "batch"},
  2356  			expectedGroupVersions: []string{"apps/v1", "batch/v1"},
  2357  			expectedGVKs: []string{
  2358  				"apps/v1/Deployment",
  2359  				"apps/v1/StatefulSet",
  2360  				"batch/v1/Job",
  2361  				"batch/v1/CronJob",
  2362  			},
  2363  			expectedFailedGVs: []string{"batch/v1beta1"},
  2364  		},
  2365  	}
  2366  
  2367  	// Ensure that client can parse both V2Beta1 and V2 types from server
  2368  	serverAccepts := []string{AcceptV2Beta1, AcceptV2}
  2369  	for _, test := range tests {
  2370  		for _, accept := range serverAccepts {
  2371  			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  2372  				var output []byte
  2373  				var err error
  2374  				if accept == AcceptV2 {
  2375  					var agg *apidiscovery.APIGroupDiscoveryList
  2376  					switch req.URL.Path {
  2377  					case "/api":
  2378  						agg = test.corev1
  2379  					case "/apis":
  2380  						agg = test.apis
  2381  					default:
  2382  						w.WriteHeader(http.StatusNotFound)
  2383  						return
  2384  					}
  2385  					output, err = json.Marshal(agg)
  2386  					require.NoError(t, err)
  2387  				} else {
  2388  					var agg *apidiscoveryv2beta1.APIGroupDiscoveryList
  2389  					switch req.URL.Path {
  2390  					case "/api":
  2391  						agg = test.corev1DiscoveryBeta
  2392  					case "/apis":
  2393  						agg = test.apisDiscoveryBeta
  2394  					default:
  2395  						w.WriteHeader(http.StatusNotFound)
  2396  						return
  2397  					}
  2398  					output, err = json.Marshal(&agg)
  2399  					require.NoError(t, err)
  2400  				}
  2401  				// Content-Type is "aggregated" discovery format. Add extra parameter
  2402  				// to ensure we are resilient to these extra parameters.
  2403  				w.Header().Set("Content-Type", accept+"; charset=utf-8")
  2404  				w.WriteHeader(http.StatusOK)
  2405  				_, err = w.Write(output)
  2406  				require.NoError(t, err)
  2407  
  2408  			}))
  2409  			defer server.Close()
  2410  			client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  2411  			apiGroups, resources, err := client.ServerGroupsAndResources()
  2412  			if len(test.expectedFailedGVs) > 0 {
  2413  				require.Error(t, err)
  2414  				expectedFailedGVs := sets.NewString(test.expectedFailedGVs...)
  2415  				actualFailedGVs := sets.NewString(failedGroupVersions(err)...)
  2416  				assert.True(t, expectedFailedGVs.Equal(actualFailedGVs),
  2417  					"%s: Expected Failed GVs (%s), got (%s)", test.name, expectedFailedGVs, actualFailedGVs)
  2418  			} else {
  2419  				require.NoError(t, err)
  2420  			}
  2421  			// Test the expected groups are returned for the aggregated format.
  2422  			expectedGroupNames := sets.NewString(test.expectedGroupNames...)
  2423  			actualGroupNames := sets.NewString(groupNames(apiGroups)...)
  2424  			assert.True(t, expectedGroupNames.Equal(actualGroupNames),
  2425  				"%s: Expected GVKs (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
  2426  			// If the core V1 group is returned from /api, it should be the first group.
  2427  			if expectedGroupNames.Has("") {
  2428  				assert.True(t, len(apiGroups) > 0)
  2429  				actualFirstGroup := apiGroups[0]
  2430  				assert.True(t, len(actualFirstGroup.Versions) > 0)
  2431  				actualFirstGroupVersion := actualFirstGroup.Versions[0].GroupVersion
  2432  				assert.Equal(t, "v1", actualFirstGroupVersion)
  2433  			}
  2434  			// Test the expected group/versions are returned from the aggregated discovery.
  2435  			expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
  2436  			actualGroupVersions := sets.NewString(groupVersions(resources)...)
  2437  			assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
  2438  				"%s: Expected GroupVersions(%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
  2439  			// Test the expected GVKs are returned from the aggregated discovery.
  2440  			expectedGVKs := sets.NewString(test.expectedGVKs...)
  2441  			actualGVKs := sets.NewString(groupVersionKinds(resources)...)
  2442  			assert.True(t, expectedGVKs.Equal(actualGVKs),
  2443  				"%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List())
  2444  		}
  2445  	}
  2446  }
  2447  
  2448  func TestAggregatedServerGroupsAndResourcesWithErrors(t *testing.T) {
  2449  	tests := []struct {
  2450  		name              string
  2451  		corev1            *apidiscovery.APIGroupDiscoveryList
  2452  		coreHttpStatus    int
  2453  		apis              *apidiscovery.APIGroupDiscoveryList
  2454  		apisHttpStatus    int
  2455  		expectedGroups    []string
  2456  		expectedResources []string
  2457  		expectedErr       bool
  2458  	}{
  2459  		{
  2460  			name:           "Aggregated Discovery: 404 for core/v1 is tolerated",
  2461  			corev1:         &apidiscovery.APIGroupDiscoveryList{},
  2462  			coreHttpStatus: http.StatusNotFound,
  2463  			apis: &apidiscovery.APIGroupDiscoveryList{
  2464  				Items: []apidiscovery.APIGroupDiscovery{
  2465  					{
  2466  						ObjectMeta: metav1.ObjectMeta{
  2467  							Name: "apps",
  2468  						},
  2469  						Versions: []apidiscovery.APIVersionDiscovery{
  2470  							{
  2471  								Version: "v1",
  2472  								Resources: []apidiscovery.APIResourceDiscovery{
  2473  									{
  2474  										Resource: "deployments",
  2475  										ResponseKind: &metav1.GroupVersionKind{
  2476  											Group:   "apps",
  2477  											Version: "v1",
  2478  											Kind:    "Deployment",
  2479  										},
  2480  										Scope: apidiscovery.ScopeNamespace,
  2481  									},
  2482  									{
  2483  										Resource: "daemonsets",
  2484  										ResponseKind: &metav1.GroupVersionKind{
  2485  											Group:   "apps",
  2486  											Version: "v1",
  2487  											Kind:    "DaemonSet",
  2488  										},
  2489  										Scope: apidiscovery.ScopeNamespace,
  2490  									},
  2491  								},
  2492  							},
  2493  						},
  2494  					},
  2495  				},
  2496  			},
  2497  			apisHttpStatus:    http.StatusOK,
  2498  			expectedGroups:    []string{"apps"},
  2499  			expectedResources: []string{"apps/v1/Deployment", "apps/v1/DaemonSet"},
  2500  			expectedErr:       false,
  2501  		},
  2502  		{
  2503  			name:           "Aggregated Discovery: 403 for core/v1 causes error",
  2504  			corev1:         &apidiscovery.APIGroupDiscoveryList{},
  2505  			coreHttpStatus: http.StatusForbidden,
  2506  			apis:           &apidiscovery.APIGroupDiscoveryList{},
  2507  			apisHttpStatus: http.StatusOK,
  2508  			expectedErr:    true,
  2509  		},
  2510  		{
  2511  			name:           "Aggregated Discovery: 404 for /apis causes error",
  2512  			corev1:         &apidiscovery.APIGroupDiscoveryList{},
  2513  			coreHttpStatus: http.StatusOK,
  2514  			apis:           &apidiscovery.APIGroupDiscoveryList{},
  2515  			apisHttpStatus: http.StatusNotFound,
  2516  			expectedErr:    true,
  2517  		},
  2518  		{
  2519  			name:           "Aggregated Discovery: 403 for /apis causes error",
  2520  			corev1:         &apidiscovery.APIGroupDiscoveryList{},
  2521  			coreHttpStatus: http.StatusOK,
  2522  			apis:           &apidiscovery.APIGroupDiscoveryList{},
  2523  			apisHttpStatus: http.StatusForbidden,
  2524  			expectedErr:    true,
  2525  		},
  2526  	}
  2527  
  2528  	for _, test := range tests {
  2529  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  2530  			var output []byte
  2531  			var err error
  2532  			var status int
  2533  			var agg *apidiscovery.APIGroupDiscoveryList
  2534  			switch req.URL.Path {
  2535  			case "/api":
  2536  				agg = test.corev1
  2537  				status = test.coreHttpStatus
  2538  			case "/apis":
  2539  				agg = test.apis
  2540  				status = test.apisHttpStatus
  2541  			default:
  2542  				w.WriteHeader(http.StatusNotFound)
  2543  				return
  2544  			}
  2545  			output, err = json.Marshal(agg)
  2546  			require.NoError(t, err)
  2547  			// Content-Type is "aggregated" discovery format. Add extra parameter
  2548  			// to ensure we are resilient to these extra parameters.
  2549  			w.Header().Set("Content-Type", AcceptV2+"; charset=utf-8")
  2550  			w.WriteHeader(status)
  2551  			_, err = w.Write(output)
  2552  			require.NoError(t, err)
  2553  		}))
  2554  		defer server.Close()
  2555  
  2556  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  2557  		apiGroups, resources, err := client.ServerGroupsAndResources()
  2558  		if test.expectedErr {
  2559  			require.Error(t, err)
  2560  			require.Nil(t, apiGroups)
  2561  			require.Nil(t, resources)
  2562  			continue
  2563  		}
  2564  		require.NoError(t, err)
  2565  		// First check the returned groups
  2566  		expectedGroups := sets.NewString(test.expectedGroups...)
  2567  		actualGroups := sets.NewString(groupNames(apiGroups)...)
  2568  		assert.True(t, expectedGroups.Equal(actualGroups),
  2569  			"%s: Expected GVKs (%s), got (%s)", test.name, expectedGroups.List(), actualGroups.List())
  2570  		// Next check the returned resources
  2571  		expectedGVKs := sets.NewString(test.expectedResources...)
  2572  		actualGVKs := sets.NewString(groupVersionKinds(resources)...)
  2573  		assert.True(t, expectedGVKs.Equal(actualGVKs),
  2574  			"%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List())
  2575  	}
  2576  }
  2577  
  2578  func TestAggregatedServerPreferredResources(t *testing.T) {
  2579  	tests := []struct {
  2580  		name              string
  2581  		corev1            *apidiscovery.APIGroupDiscoveryList
  2582  		apis              *apidiscovery.APIGroupDiscoveryList
  2583  		expectedGVKs      []string
  2584  		expectedFailedGVs []string
  2585  	}{
  2586  		{
  2587  			name: "Aggregated discovery: basic corev1 and apps/v1 preferred resources returned",
  2588  			corev1: &apidiscovery.APIGroupDiscoveryList{
  2589  				Items: []apidiscovery.APIGroupDiscovery{
  2590  					{
  2591  						Versions: []apidiscovery.APIVersionDiscovery{
  2592  							{
  2593  								Version: "v1",
  2594  								Resources: []apidiscovery.APIResourceDiscovery{
  2595  									{
  2596  										Resource: "pods",
  2597  										ResponseKind: &metav1.GroupVersionKind{
  2598  											Group:   "",
  2599  											Version: "v1",
  2600  											Kind:    "Pod",
  2601  										},
  2602  										Scope: apidiscovery.ScopeNamespace,
  2603  									},
  2604  								},
  2605  							},
  2606  						},
  2607  					},
  2608  				},
  2609  			},
  2610  			apis: &apidiscovery.APIGroupDiscoveryList{
  2611  				Items: []apidiscovery.APIGroupDiscovery{
  2612  					{
  2613  						ObjectMeta: metav1.ObjectMeta{
  2614  							Name: "apps",
  2615  						},
  2616  						Versions: []apidiscovery.APIVersionDiscovery{
  2617  							{
  2618  								Version: "v1",
  2619  								Resources: []apidiscovery.APIResourceDiscovery{
  2620  									{
  2621  										Resource: "deployments",
  2622  										ResponseKind: &metav1.GroupVersionKind{
  2623  											Group:   "apps",
  2624  											Version: "v1",
  2625  											Kind:    "Deployment",
  2626  										},
  2627  										Scope: apidiscovery.ScopeNamespace,
  2628  									},
  2629  								},
  2630  							},
  2631  						},
  2632  					},
  2633  				},
  2634  			},
  2635  			expectedGVKs: []string{
  2636  				"/v1/Pod",
  2637  				"apps/v1/Deployment",
  2638  			},
  2639  		},
  2640  		{
  2641  			name: "Aggregated discovery: only resources from preferred apps/v2 group/version",
  2642  			corev1: &apidiscovery.APIGroupDiscoveryList{
  2643  				Items: []apidiscovery.APIGroupDiscovery{
  2644  					{
  2645  						Versions: []apidiscovery.APIVersionDiscovery{
  2646  							{
  2647  								Version: "v1",
  2648  								Resources: []apidiscovery.APIResourceDiscovery{
  2649  									{
  2650  										Resource: "pods",
  2651  										ResponseKind: &metav1.GroupVersionKind{
  2652  											Group:   "",
  2653  											Version: "v1",
  2654  											Kind:    "Pod",
  2655  										},
  2656  										Scope: apidiscovery.ScopeNamespace,
  2657  									},
  2658  								},
  2659  							},
  2660  						},
  2661  					},
  2662  				},
  2663  			},
  2664  			apis: &apidiscovery.APIGroupDiscoveryList{
  2665  				Items: []apidiscovery.APIGroupDiscovery{
  2666  					{
  2667  						ObjectMeta: metav1.ObjectMeta{
  2668  							Name: "apps",
  2669  						},
  2670  						Versions: []apidiscovery.APIVersionDiscovery{
  2671  							// v2 is "preferred version since it is first
  2672  							{
  2673  								Version: "v2",
  2674  								Resources: []apidiscovery.APIResourceDiscovery{
  2675  									{
  2676  										Resource: "deployments",
  2677  										ResponseKind: &metav1.GroupVersionKind{
  2678  											Group:   "apps",
  2679  											Version: "v2",
  2680  											Kind:    "Deployment",
  2681  										},
  2682  										Scope: apidiscovery.ScopeNamespace,
  2683  									},
  2684  								},
  2685  							},
  2686  							{
  2687  								Version: "v1",
  2688  								Resources: []apidiscovery.APIResourceDiscovery{
  2689  									{
  2690  										Resource: "deployments",
  2691  										ResponseKind: &metav1.GroupVersionKind{
  2692  											Group:   "apps",
  2693  											Version: "v1",
  2694  											Kind:    "Deployment",
  2695  										},
  2696  										Scope: apidiscovery.ScopeNamespace,
  2697  									},
  2698  								},
  2699  							},
  2700  						},
  2701  					},
  2702  				},
  2703  			},
  2704  			// Only v2 resources from apps group, since v2 is preferred version.
  2705  			expectedGVKs: []string{
  2706  				"/v1/Pod",
  2707  				"apps/v2/Deployment",
  2708  			},
  2709  		},
  2710  		{
  2711  			name: "Aggregated discovery: stale Group/Version can not produce preferred version",
  2712  			corev1: &apidiscovery.APIGroupDiscoveryList{
  2713  				Items: []apidiscovery.APIGroupDiscovery{
  2714  					{
  2715  						Versions: []apidiscovery.APIVersionDiscovery{
  2716  							{
  2717  								Version: "v1",
  2718  								Resources: []apidiscovery.APIResourceDiscovery{
  2719  									{
  2720  										Resource: "pods",
  2721  										ResponseKind: &metav1.GroupVersionKind{
  2722  											Group:   "",
  2723  											Version: "v1",
  2724  											Kind:    "Pod",
  2725  										},
  2726  										Scope: apidiscovery.ScopeNamespace,
  2727  									},
  2728  								},
  2729  							},
  2730  						},
  2731  					},
  2732  				},
  2733  			},
  2734  			apis: &apidiscovery.APIGroupDiscoveryList{
  2735  				Items: []apidiscovery.APIGroupDiscovery{
  2736  					{
  2737  						ObjectMeta: metav1.ObjectMeta{
  2738  							Name: "apps",
  2739  						},
  2740  						Versions: []apidiscovery.APIVersionDiscovery{
  2741  							// v2 is "stale", so it can not be "preferred".
  2742  							{
  2743  								Version: "v2",
  2744  								Resources: []apidiscovery.APIResourceDiscovery{
  2745  									{
  2746  										Resource: "deployments",
  2747  										ResponseKind: &metav1.GroupVersionKind{
  2748  											Group:   "apps",
  2749  											Version: "v2",
  2750  											Kind:    "Deployment",
  2751  										},
  2752  										Scope: apidiscovery.ScopeNamespace,
  2753  									},
  2754  								},
  2755  								Freshness: apidiscovery.DiscoveryFreshnessStale,
  2756  							},
  2757  							{
  2758  								Version: "v1",
  2759  								Resources: []apidiscovery.APIResourceDiscovery{
  2760  									{
  2761  										Resource: "deployments",
  2762  										ResponseKind: &metav1.GroupVersionKind{
  2763  											Group:   "apps",
  2764  											Version: "v1",
  2765  											Kind:    "Deployment",
  2766  										},
  2767  										Scope: apidiscovery.ScopeNamespace,
  2768  									},
  2769  								},
  2770  							},
  2771  						},
  2772  					},
  2773  				},
  2774  			},
  2775  			// Only v1 resources from apps group; v2 would be preferred but it is "stale".
  2776  			expectedGVKs: []string{
  2777  				"/v1/Pod",
  2778  				"apps/v1/Deployment",
  2779  			},
  2780  			expectedFailedGVs: []string{"apps/v2"},
  2781  		},
  2782  		{
  2783  			name: "Aggregated discovery: preferred multiple resources from multiple group/versions",
  2784  			corev1: &apidiscovery.APIGroupDiscoveryList{
  2785  				Items: []apidiscovery.APIGroupDiscovery{
  2786  					{
  2787  						Versions: []apidiscovery.APIVersionDiscovery{
  2788  							{
  2789  								Version: "v1",
  2790  								Resources: []apidiscovery.APIResourceDiscovery{
  2791  									{
  2792  										Resource: "pods",
  2793  										ResponseKind: &metav1.GroupVersionKind{
  2794  											Group:   "",
  2795  											Version: "v1",
  2796  											Kind:    "Pod",
  2797  										},
  2798  										Scope: apidiscovery.ScopeNamespace,
  2799  									},
  2800  									{
  2801  										Resource: "services",
  2802  										ResponseKind: &metav1.GroupVersionKind{
  2803  											Group:   "",
  2804  											Version: "v1",
  2805  											Kind:    "Service",
  2806  										},
  2807  										Scope: apidiscovery.ScopeNamespace,
  2808  									},
  2809  								},
  2810  							},
  2811  						},
  2812  					},
  2813  				},
  2814  			},
  2815  			apis: &apidiscovery.APIGroupDiscoveryList{
  2816  				Items: []apidiscovery.APIGroupDiscovery{
  2817  					{
  2818  						ObjectMeta: metav1.ObjectMeta{
  2819  							Name: "apps",
  2820  						},
  2821  						Versions: []apidiscovery.APIVersionDiscovery{
  2822  							{
  2823  								Version: "v1",
  2824  								Resources: []apidiscovery.APIResourceDiscovery{
  2825  									{
  2826  										Resource: "deployments",
  2827  										ResponseKind: &metav1.GroupVersionKind{
  2828  											Group:   "apps",
  2829  											Version: "v1",
  2830  											Kind:    "Deployment",
  2831  										},
  2832  										Scope: apidiscovery.ScopeNamespace,
  2833  									},
  2834  									{
  2835  										Resource: "statefulsets",
  2836  										ResponseKind: &metav1.GroupVersionKind{
  2837  											Group:   "apps",
  2838  											Version: "v1",
  2839  											Kind:    "StatefulSet",
  2840  										},
  2841  										Scope: apidiscovery.ScopeNamespace,
  2842  									},
  2843  								},
  2844  							},
  2845  							{
  2846  								Version: "v1beta1",
  2847  								Resources: []apidiscovery.APIResourceDiscovery{
  2848  									{
  2849  										Resource: "deployments",
  2850  										ResponseKind: &metav1.GroupVersionKind{
  2851  											Group:   "apps",
  2852  											Version: "v1beta1",
  2853  											Kind:    "Deployment",
  2854  										},
  2855  										Scope: apidiscovery.ScopeNamespace,
  2856  									},
  2857  									{
  2858  										Resource: "statefulsets",
  2859  										ResponseKind: &metav1.GroupVersionKind{
  2860  											Group:   "apps",
  2861  											Version: "v1beta1",
  2862  											Kind:    "StatefulSet",
  2863  										},
  2864  										Scope: apidiscovery.ScopeNamespace,
  2865  									},
  2866  								},
  2867  								Freshness: apidiscovery.DiscoveryFreshnessStale,
  2868  							},
  2869  						},
  2870  					},
  2871  				},
  2872  			},
  2873  			expectedGVKs: []string{
  2874  				"/v1/Pod",
  2875  				"/v1/Service",
  2876  				"apps/v1/Deployment",
  2877  				"apps/v1/StatefulSet",
  2878  			},
  2879  			expectedFailedGVs: []string{"apps/v1beta1"},
  2880  		},
  2881  		{
  2882  			name: "Aggregated discovery: resources from multiple preferred group versions at /apis",
  2883  			corev1: &apidiscovery.APIGroupDiscoveryList{
  2884  				Items: []apidiscovery.APIGroupDiscovery{
  2885  					{
  2886  						Versions: []apidiscovery.APIVersionDiscovery{
  2887  							{
  2888  								Version: "v1",
  2889  								Resources: []apidiscovery.APIResourceDiscovery{
  2890  									{
  2891  										Resource: "pods",
  2892  										ResponseKind: &metav1.GroupVersionKind{
  2893  											Group:   "",
  2894  											Version: "v1",
  2895  											Kind:    "Pod",
  2896  										},
  2897  										Scope: apidiscovery.ScopeNamespace,
  2898  									},
  2899  									{
  2900  										Resource: "services",
  2901  										ResponseKind: &metav1.GroupVersionKind{
  2902  											Group:   "",
  2903  											Version: "v1",
  2904  											Kind:    "Service",
  2905  										},
  2906  										Scope: apidiscovery.ScopeNamespace,
  2907  									},
  2908  								},
  2909  							},
  2910  						},
  2911  					},
  2912  				},
  2913  			},
  2914  			apis: &apidiscovery.APIGroupDiscoveryList{
  2915  				Items: []apidiscovery.APIGroupDiscovery{
  2916  					{
  2917  						ObjectMeta: metav1.ObjectMeta{
  2918  							Name: "apps",
  2919  						},
  2920  						Versions: []apidiscovery.APIVersionDiscovery{
  2921  							{
  2922  								Version: "v1",
  2923  								Resources: []apidiscovery.APIResourceDiscovery{
  2924  									{
  2925  										Resource: "deployments",
  2926  										ResponseKind: &metav1.GroupVersionKind{
  2927  											Group:   "apps",
  2928  											Version: "v1",
  2929  											Kind:    "Deployment",
  2930  										},
  2931  										Scope: apidiscovery.ScopeNamespace,
  2932  									},
  2933  									{
  2934  										Resource: "statefulsets",
  2935  										ResponseKind: &metav1.GroupVersionKind{
  2936  											Group:   "apps",
  2937  											Version: "v1",
  2938  											Kind:    "StatefulSet",
  2939  										},
  2940  										Scope: apidiscovery.ScopeNamespace,
  2941  									},
  2942  								},
  2943  							},
  2944  							{
  2945  								// Not included because "v1" is preferred.
  2946  								Version: "v1beta1",
  2947  								Resources: []apidiscovery.APIResourceDiscovery{
  2948  									{
  2949  										Resource: "deployments",
  2950  										ResponseKind: &metav1.GroupVersionKind{
  2951  											Group:   "apps",
  2952  											Version: "v1beta1",
  2953  											Kind:    "Deployment",
  2954  										},
  2955  										Scope: apidiscovery.ScopeNamespace,
  2956  									},
  2957  									{
  2958  										Resource: "statefulsets",
  2959  										ResponseKind: &metav1.GroupVersionKind{
  2960  											Group:   "apps",
  2961  											Version: "v1beta1",
  2962  											Kind:    "StatefulSet",
  2963  										},
  2964  										Scope: apidiscovery.ScopeNamespace,
  2965  									},
  2966  								},
  2967  							},
  2968  						},
  2969  					},
  2970  					{
  2971  						ObjectMeta: metav1.ObjectMeta{
  2972  							Name: "batch",
  2973  						},
  2974  						Versions: []apidiscovery.APIVersionDiscovery{
  2975  							{
  2976  								Version: "v1",
  2977  								Resources: []apidiscovery.APIResourceDiscovery{
  2978  									{
  2979  										Resource: "jobs",
  2980  										ResponseKind: &metav1.GroupVersionKind{
  2981  											Group:   "batch",
  2982  											Version: "v1",
  2983  											Kind:    "Job",
  2984  										},
  2985  										Scope: apidiscovery.ScopeNamespace,
  2986  									},
  2987  									{
  2988  										Resource: "cronjobs",
  2989  										ResponseKind: &metav1.GroupVersionKind{
  2990  											Group:   "batch",
  2991  											Version: "v1",
  2992  											Kind:    "CronJob",
  2993  										},
  2994  										Scope: apidiscovery.ScopeNamespace,
  2995  									},
  2996  								},
  2997  							},
  2998  						},
  2999  					},
  3000  				},
  3001  			},
  3002  			expectedGVKs: []string{
  3003  				"/v1/Pod",
  3004  				"/v1/Service",
  3005  				"apps/v1/Deployment",
  3006  				"apps/v1/StatefulSet",
  3007  				"batch/v1/Job",
  3008  				"batch/v1/CronJob",
  3009  			},
  3010  		},
  3011  		{
  3012  			name: "Aggregated discovery: resources from only preferred group versions for batch group",
  3013  			corev1: &apidiscovery.APIGroupDiscoveryList{
  3014  				Items: []apidiscovery.APIGroupDiscovery{
  3015  					{
  3016  						Versions: []apidiscovery.APIVersionDiscovery{
  3017  							{
  3018  								Version: "v1",
  3019  								Resources: []apidiscovery.APIResourceDiscovery{
  3020  									{
  3021  										Resource: "pods",
  3022  										ResponseKind: &metav1.GroupVersionKind{
  3023  											Group:   "",
  3024  											Version: "v1",
  3025  											Kind:    "Pod",
  3026  										},
  3027  										Scope: apidiscovery.ScopeNamespace,
  3028  									},
  3029  									{
  3030  										Resource: "services",
  3031  										ResponseKind: &metav1.GroupVersionKind{
  3032  											Group:   "",
  3033  											Version: "v1",
  3034  											Kind:    "Service",
  3035  										},
  3036  										Scope: apidiscovery.ScopeNamespace,
  3037  									},
  3038  								},
  3039  							},
  3040  						},
  3041  					},
  3042  				},
  3043  			},
  3044  			apis: &apidiscovery.APIGroupDiscoveryList{
  3045  				Items: []apidiscovery.APIGroupDiscovery{
  3046  					{
  3047  						ObjectMeta: metav1.ObjectMeta{
  3048  							Name: "apps",
  3049  						},
  3050  						Versions: []apidiscovery.APIVersionDiscovery{
  3051  							{
  3052  								Version: "v1",
  3053  								Resources: []apidiscovery.APIResourceDiscovery{
  3054  									{
  3055  										Resource: "deployments",
  3056  										ResponseKind: &metav1.GroupVersionKind{
  3057  											Group:   "apps",
  3058  											Version: "v1",
  3059  											Kind:    "Deployment",
  3060  										},
  3061  										Scope: apidiscovery.ScopeNamespace,
  3062  									},
  3063  									{
  3064  										Resource: "statefulsets",
  3065  										ResponseKind: &metav1.GroupVersionKind{
  3066  											Group:   "apps",
  3067  											Version: "v1",
  3068  											Kind:    "StatefulSet",
  3069  										},
  3070  										Scope: apidiscovery.ScopeNamespace,
  3071  									},
  3072  								},
  3073  							},
  3074  						},
  3075  					},
  3076  					{
  3077  						ObjectMeta: metav1.ObjectMeta{
  3078  							Name: "batch",
  3079  						},
  3080  						Versions: []apidiscovery.APIVersionDiscovery{
  3081  							{
  3082  								Version: "v1",
  3083  								Resources: []apidiscovery.APIResourceDiscovery{
  3084  									{
  3085  										Resource: "jobs",
  3086  										ResponseKind: &metav1.GroupVersionKind{
  3087  											Group:   "batch",
  3088  											Version: "v1",
  3089  											Kind:    "Job",
  3090  										},
  3091  										Scope: apidiscovery.ScopeNamespace,
  3092  									},
  3093  									{
  3094  										Resource: "cronjobs",
  3095  										ResponseKind: &metav1.GroupVersionKind{
  3096  											Group:   "batch",
  3097  											Version: "v1",
  3098  											Kind:    "CronJob",
  3099  										},
  3100  										Scope: apidiscovery.ScopeNamespace,
  3101  									},
  3102  								},
  3103  							},
  3104  							{
  3105  								// Not included, since "v1" is preferred.
  3106  								Version: "v1beta1",
  3107  								Resources: []apidiscovery.APIResourceDiscovery{
  3108  									{
  3109  										Resource: "jobs",
  3110  										ResponseKind: &metav1.GroupVersionKind{
  3111  											Group:   "batch",
  3112  											Version: "v1beta1",
  3113  											Kind:    "Job",
  3114  										},
  3115  										Scope: apidiscovery.ScopeNamespace,
  3116  									},
  3117  									{
  3118  										Resource: "cronjobs",
  3119  										ResponseKind: &metav1.GroupVersionKind{
  3120  											Group:   "batch",
  3121  											Version: "v1beta1",
  3122  											Kind:    "CronJob",
  3123  										},
  3124  										Scope: apidiscovery.ScopeNamespace,
  3125  									},
  3126  								},
  3127  							},
  3128  						},
  3129  					},
  3130  				},
  3131  			},
  3132  			// Only preferred resources expected--not batch/v1beta1 resources.
  3133  			expectedGVKs: []string{
  3134  				"/v1/Pod",
  3135  				"/v1/Service",
  3136  				"apps/v1/Deployment",
  3137  				"apps/v1/StatefulSet",
  3138  				"batch/v1/Job",
  3139  				"batch/v1/CronJob",
  3140  			},
  3141  		},
  3142  	}
  3143  
  3144  	for _, test := range tests {
  3145  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  3146  			var output []byte
  3147  			var err error
  3148  			var agg *apidiscovery.APIGroupDiscoveryList
  3149  			switch req.URL.Path {
  3150  			case "/api":
  3151  				agg = test.corev1
  3152  			case "/apis":
  3153  				agg = test.apis
  3154  			default:
  3155  				w.WriteHeader(http.StatusNotFound)
  3156  				return
  3157  			}
  3158  			output, err = json.Marshal(agg)
  3159  			require.NoError(t, err)
  3160  			// Content-Type is "aggregated" discovery format. Add extra parameter
  3161  			// to ensure we are resilient to these extra parameters.
  3162  			w.Header().Set("Content-Type", AcceptV2+"; charset=utf-8")
  3163  			w.WriteHeader(http.StatusOK)
  3164  			_, err = w.Write(output)
  3165  			require.NoError(t, err)
  3166  		}))
  3167  		defer server.Close()
  3168  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  3169  		resources, err := client.ServerPreferredResources()
  3170  		if len(test.expectedFailedGVs) > 0 {
  3171  			require.Error(t, err)
  3172  			expectedFailedGVs := sets.NewString(test.expectedFailedGVs...)
  3173  			actualFailedGVs := sets.NewString(failedGroupVersions(err)...)
  3174  			assert.True(t, expectedFailedGVs.Equal(actualFailedGVs),
  3175  				"%s: Expected Failed GVs (%s), got (%s)", test.name, expectedFailedGVs, actualFailedGVs)
  3176  		} else {
  3177  			require.NoError(t, err)
  3178  		}
  3179  		// Test the expected preferred GVKs are returned from the aggregated discovery.
  3180  		expectedGVKs := sets.NewString(test.expectedGVKs...)
  3181  		actualGVKs := sets.NewString(groupVersionKinds(resources)...)
  3182  		assert.True(t, expectedGVKs.Equal(actualGVKs),
  3183  			"%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List())
  3184  	}
  3185  }
  3186  
  3187  func TestDiscoveryContentTypeVersion(t *testing.T) {
  3188  	v2 := schema.GroupVersionKind{Group: "apidiscovery.k8s.io", Version: "v2", Kind: "APIGroupDiscoveryList"}
  3189  	tests := []struct {
  3190  		contentType string
  3191  		gvk         schema.GroupVersionKind
  3192  		match       bool
  3193  		expectErr   bool
  3194  	}{
  3195  		{
  3196  			contentType: "application/json; g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList",
  3197  			gvk:         v2,
  3198  			match:       true,
  3199  			expectErr:   false,
  3200  		},
  3201  		{
  3202  			// content-type parameters are not in correct order, but comparison ignores order.
  3203  			contentType: "application/json; v=v2;as=APIGroupDiscoveryList;g=apidiscovery.k8s.io",
  3204  			gvk:         v2,
  3205  			match:       true,
  3206  			expectErr:   false,
  3207  		},
  3208  		{
  3209  			// content-type parameters are not in correct order, but comparison ignores order.
  3210  			contentType: "application/json; as=APIGroupDiscoveryList;g=apidiscovery.k8s.io;v=v2",
  3211  			gvk:         v2,
  3212  			match:       true,
  3213  			expectErr:   false,
  3214  		},
  3215  		{
  3216  			// Ignores extra parameter "charset=utf-8"
  3217  			contentType: "application/json; g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList;charset=utf-8",
  3218  			gvk:         v2,
  3219  			match:       true,
  3220  			expectErr:   false,
  3221  		},
  3222  		{
  3223  			contentType: "application/json",
  3224  			gvk:         v2,
  3225  			match:       false,
  3226  			expectErr:   false,
  3227  		},
  3228  		{
  3229  			contentType: "application/json; charset=UTF-8",
  3230  			gvk:         v2,
  3231  			match:       false,
  3232  			expectErr:   false,
  3233  		},
  3234  		{
  3235  			contentType: "text/json",
  3236  			gvk:         v2,
  3237  			match:       false,
  3238  			expectErr:   false,
  3239  		},
  3240  		{
  3241  			contentType: "text/html",
  3242  			gvk:         v2,
  3243  			match:       false,
  3244  			expectErr:   false,
  3245  		},
  3246  		{
  3247  			contentType: "",
  3248  			gvk:         v2,
  3249  			match:       false,
  3250  			expectErr:   true,
  3251  		},
  3252  	}
  3253  
  3254  	for _, test := range tests {
  3255  		match, err := ContentTypeIsGVK(test.contentType, test.gvk)
  3256  		assert.Equal(t, test.expectErr, err != nil)
  3257  		assert.Equal(t, test.match, match)
  3258  	}
  3259  }
  3260  
  3261  func TestUseLegacyDiscovery(t *testing.T) {
  3262  	// Default client sends aggregated discovery accept format (first) as well as legacy format.
  3263  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  3264  		acceptHeader := req.Header.Get("Accept")
  3265  		assert.Equal(t, acceptDiscoveryFormats, acceptHeader)
  3266  	}))
  3267  	defer server.Close()
  3268  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  3269  	client.ServerGroups()
  3270  	// When "UseLegacyDiscovery" field is set, only the legacy discovery format is requested.
  3271  	server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  3272  		acceptHeader := req.Header.Get("Accept")
  3273  		assert.Equal(t, AcceptV1, acceptHeader)
  3274  	}))
  3275  	defer server.Close()
  3276  	client = NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
  3277  	client.UseLegacyDiscovery = true
  3278  	client.ServerGroups()
  3279  }
  3280  
  3281  func groupNames(groups []*metav1.APIGroup) []string {
  3282  	result := []string{}
  3283  	for _, group := range groups {
  3284  		result = append(result, group.Name)
  3285  	}
  3286  	return result
  3287  }
  3288  
  3289  func groupNamesFromList(groups *metav1.APIGroupList) []string {
  3290  	result := []string{}
  3291  	for _, group := range groups.Groups {
  3292  		result = append(result, group.Name)
  3293  	}
  3294  	return result
  3295  }
  3296  
  3297  func preferredVersionsFromList(groups *metav1.APIGroupList) []string {
  3298  	result := []string{}
  3299  	for _, group := range groups.Groups {
  3300  		preferredGV := group.PreferredVersion.GroupVersion
  3301  		result = append(result, preferredGV)
  3302  	}
  3303  	return result
  3304  }
  3305  
  3306  func groupVersions(resources []*metav1.APIResourceList) []string {
  3307  	result := []string{}
  3308  	for _, resourceList := range resources {
  3309  		result = append(result, resourceList.GroupVersion)
  3310  	}
  3311  	return result
  3312  }
  3313  
  3314  func groupVersionsFromGroups(groups *metav1.APIGroupList) []string {
  3315  	result := []string{}
  3316  	for _, group := range groups.Groups {
  3317  		for _, version := range group.Versions {
  3318  			result = append(result, version.GroupVersion)
  3319  		}
  3320  	}
  3321  	return result
  3322  }
  3323  
  3324  func groupVersionKinds(resources []*metav1.APIResourceList) []string {
  3325  	result := []string{}
  3326  	for _, resourceList := range resources {
  3327  		for _, resource := range resourceList.APIResources {
  3328  			gvk := fmt.Sprintf("%s/%s/%s", resource.Group, resource.Version, resource.Kind)
  3329  			result = append(result, gvk)
  3330  		}
  3331  	}
  3332  	return result
  3333  }
  3334  
  3335  func failedGroupVersions(err error) []string {
  3336  	result := []string{}
  3337  	ferr, ok := err.(*ErrGroupDiscoveryFailed)
  3338  	if !ok {
  3339  		return result
  3340  	}
  3341  	for gv := range ferr.Groups {
  3342  		result = append(result, gv.String())
  3343  	}
  3344  	return result
  3345  }
  3346  

View as plain text