k8s.io/client-go@v0.22.2/discovery/discovery_client_test.go (about)

     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  	"mime"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"reflect"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/gogo/protobuf/proto"
    30  	openapi_v2 "github.com/googleapis/gnostic/openapiv2"
    31  	"github.com/stretchr/testify/assert"
    32  	golangproto "google.golang.org/protobuf/proto"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/apimachinery/pkg/util/diff"
    36  	"k8s.io/apimachinery/pkg/util/sets"
    37  	"k8s.io/apimachinery/pkg/version"
    38  	restclient "k8s.io/client-go/rest"
    39  )
    40  
    41  func TestGetServerVersion(t *testing.T) {
    42  	expect := version.Info{
    43  		Major:     "foo",
    44  		Minor:     "bar",
    45  		GitCommit: "baz",
    46  	}
    47  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    48  		output, err := json.Marshal(expect)
    49  		if err != nil {
    50  			t.Errorf("unexpected encoding error: %v", err)
    51  			return
    52  		}
    53  		w.Header().Set("Content-Type", "application/json")
    54  		w.WriteHeader(http.StatusOK)
    55  		w.Write(output)
    56  	}))
    57  	defer server.Close()
    58  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
    59  
    60  	got, err := client.ServerVersion()
    61  	if err != nil {
    62  		t.Fatalf("unexpected encoding error: %v", err)
    63  	}
    64  	if e, a := expect, *got; !reflect.DeepEqual(e, a) {
    65  		t.Errorf("expected %v, got %v", e, a)
    66  	}
    67  }
    68  
    69  func TestGetServerGroupsWithV1Server(t *testing.T) {
    70  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    71  		var obj interface{}
    72  		switch req.URL.Path {
    73  		case "/api":
    74  			obj = &metav1.APIVersions{
    75  				Versions: []string{
    76  					"v1",
    77  				},
    78  			}
    79  		case "/apis":
    80  			obj = &metav1.APIGroupList{
    81  				Groups: []metav1.APIGroup{
    82  					{
    83  						Name: "extensions",
    84  						Versions: []metav1.GroupVersionForDiscovery{
    85  							{GroupVersion: "extensions/v1beta1"},
    86  						},
    87  					},
    88  				},
    89  			}
    90  		default:
    91  			w.WriteHeader(http.StatusNotFound)
    92  			return
    93  		}
    94  		output, err := json.Marshal(obj)
    95  		if err != nil {
    96  			t.Fatalf("unexpected encoding error: %v", err)
    97  			return
    98  		}
    99  		w.Header().Set("Content-Type", "application/json")
   100  		w.WriteHeader(http.StatusOK)
   101  		w.Write(output)
   102  	}))
   103  	defer server.Close()
   104  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   105  	// ServerGroups should not return an error even if server returns error at /api and /apis
   106  	apiGroupList, err := client.ServerGroups()
   107  	if err != nil {
   108  		t.Fatalf("unexpected error: %v", err)
   109  	}
   110  	groupVersions := metav1.ExtractGroupVersions(apiGroupList)
   111  	if !reflect.DeepEqual(groupVersions, []string{"v1", "extensions/v1beta1"}) {
   112  		t.Errorf("expected: %q, got: %q", []string{"v1", "extensions/v1beta1"}, groupVersions)
   113  	}
   114  }
   115  
   116  func TestGetServerGroupsWithBrokenServer(t *testing.T) {
   117  	for _, statusCode := range []int{http.StatusNotFound, http.StatusForbidden} {
   118  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   119  			w.WriteHeader(statusCode)
   120  		}))
   121  		defer server.Close()
   122  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   123  		// ServerGroups should not return an error even if server returns Not Found or Forbidden error at all end points
   124  		apiGroupList, err := client.ServerGroups()
   125  		if err != nil {
   126  			t.Fatalf("unexpected error: %v", err)
   127  		}
   128  		groupVersions := metav1.ExtractGroupVersions(apiGroupList)
   129  		if len(groupVersions) != 0 {
   130  			t.Errorf("expected empty list, got: %q", groupVersions)
   131  		}
   132  	}
   133  }
   134  
   135  func TestTimeoutIsSet(t *testing.T) {
   136  	cfg := &restclient.Config{}
   137  	setDiscoveryDefaults(cfg)
   138  	assert.Equal(t, defaultTimeout, cfg.Timeout)
   139  }
   140  
   141  func TestGetServerResourcesWithV1Server(t *testing.T) {
   142  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   143  		var obj interface{}
   144  		switch req.URL.Path {
   145  		case "/api":
   146  			obj = &metav1.APIVersions{
   147  				Versions: []string{
   148  					"v1",
   149  				},
   150  			}
   151  		default:
   152  			w.WriteHeader(http.StatusNotFound)
   153  			return
   154  		}
   155  		output, err := json.Marshal(obj)
   156  		if err != nil {
   157  			t.Errorf("unexpected encoding error: %v", err)
   158  			return
   159  		}
   160  		w.Header().Set("Content-Type", "application/json")
   161  		w.WriteHeader(http.StatusOK)
   162  		w.Write(output)
   163  	}))
   164  	defer server.Close()
   165  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   166  	// ServerResources should not return an error even if server returns error at /api/v1.
   167  	serverResources, err := client.ServerResources()
   168  	if err != nil {
   169  		t.Errorf("unexpected error: %v", err)
   170  	}
   171  	gvs := groupVersions(serverResources)
   172  	if !sets.NewString(gvs...).Has("v1") {
   173  		t.Errorf("missing v1 in resource list: %v", serverResources)
   174  	}
   175  }
   176  
   177  func TestGetServerResources(t *testing.T) {
   178  	stable := metav1.APIResourceList{
   179  		GroupVersion: "v1",
   180  		APIResources: []metav1.APIResource{
   181  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   182  			{Name: "services", Namespaced: true, Kind: "Service"},
   183  			{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
   184  		},
   185  	}
   186  	beta := metav1.APIResourceList{
   187  		GroupVersion: "extensions/v1beta1",
   188  		APIResources: []metav1.APIResource{
   189  			{Name: "deployments", Namespaced: true, Kind: "Deployment"},
   190  			{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
   191  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   192  		},
   193  	}
   194  	beta2 := metav1.APIResourceList{
   195  		GroupVersion: "extensions/v1beta2",
   196  		APIResources: []metav1.APIResource{
   197  			{Name: "deployments", Namespaced: true, Kind: "Deployment"},
   198  			{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
   199  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   200  		},
   201  	}
   202  	extensionsbeta3 := metav1.APIResourceList{GroupVersion: "extensions/v1beta3", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   203  	extensionsbeta4 := metav1.APIResourceList{GroupVersion: "extensions/v1beta4", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   204  	extensionsbeta5 := metav1.APIResourceList{GroupVersion: "extensions/v1beta5", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   205  	extensionsbeta6 := metav1.APIResourceList{GroupVersion: "extensions/v1beta6", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   206  	extensionsbeta7 := metav1.APIResourceList{GroupVersion: "extensions/v1beta7", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   207  	extensionsbeta8 := metav1.APIResourceList{GroupVersion: "extensions/v1beta8", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   208  	extensionsbeta9 := metav1.APIResourceList{GroupVersion: "extensions/v1beta9", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   209  	extensionsbeta10 := metav1.APIResourceList{GroupVersion: "extensions/v1beta10", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   210  
   211  	appsbeta1 := metav1.APIResourceList{GroupVersion: "apps/v1beta1", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   212  	appsbeta2 := metav1.APIResourceList{GroupVersion: "apps/v1beta2", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   213  	appsbeta3 := metav1.APIResourceList{GroupVersion: "apps/v1beta3", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   214  	appsbeta4 := metav1.APIResourceList{GroupVersion: "apps/v1beta4", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   215  	appsbeta5 := metav1.APIResourceList{GroupVersion: "apps/v1beta5", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   216  	appsbeta6 := metav1.APIResourceList{GroupVersion: "apps/v1beta6", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   217  	appsbeta7 := metav1.APIResourceList{GroupVersion: "apps/v1beta7", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   218  	appsbeta8 := metav1.APIResourceList{GroupVersion: "apps/v1beta8", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   219  	appsbeta9 := metav1.APIResourceList{GroupVersion: "apps/v1beta9", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   220  	appsbeta10 := metav1.APIResourceList{GroupVersion: "apps/v1beta10", APIResources: []metav1.APIResource{{Name: "deployments", Namespaced: true, Kind: "Deployment"}}}
   221  
   222  	tests := []struct {
   223  		resourcesList *metav1.APIResourceList
   224  		path          string
   225  		request       string
   226  		expectErr     bool
   227  	}{
   228  		{
   229  			resourcesList: &stable,
   230  			path:          "/api/v1",
   231  			request:       "v1",
   232  			expectErr:     false,
   233  		},
   234  		{
   235  			resourcesList: &beta,
   236  			path:          "/apis/extensions/v1beta1",
   237  			request:       "extensions/v1beta1",
   238  			expectErr:     false,
   239  		},
   240  		{
   241  			resourcesList: &stable,
   242  			path:          "/api/v1",
   243  			request:       "foobar",
   244  			expectErr:     true,
   245  		},
   246  	}
   247  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   248  		var list interface{}
   249  		switch req.URL.Path {
   250  		case "/api/v1":
   251  			list = &stable
   252  		case "/apis/extensions/v1beta1":
   253  			list = &beta
   254  		case "/apis/extensions/v1beta2":
   255  			list = &beta2
   256  		case "/apis/extensions/v1beta3":
   257  			list = &extensionsbeta3
   258  		case "/apis/extensions/v1beta4":
   259  			list = &extensionsbeta4
   260  		case "/apis/extensions/v1beta5":
   261  			list = &extensionsbeta5
   262  		case "/apis/extensions/v1beta6":
   263  			list = &extensionsbeta6
   264  		case "/apis/extensions/v1beta7":
   265  			list = &extensionsbeta7
   266  		case "/apis/extensions/v1beta8":
   267  			list = &extensionsbeta8
   268  		case "/apis/extensions/v1beta9":
   269  			list = &extensionsbeta9
   270  		case "/apis/extensions/v1beta10":
   271  			list = &extensionsbeta10
   272  		case "/apis/apps/v1beta1":
   273  			list = &appsbeta1
   274  		case "/apis/apps/v1beta2":
   275  			list = &appsbeta2
   276  		case "/apis/apps/v1beta3":
   277  			list = &appsbeta3
   278  		case "/apis/apps/v1beta4":
   279  			list = &appsbeta4
   280  		case "/apis/apps/v1beta5":
   281  			list = &appsbeta5
   282  		case "/apis/apps/v1beta6":
   283  			list = &appsbeta6
   284  		case "/apis/apps/v1beta7":
   285  			list = &appsbeta7
   286  		case "/apis/apps/v1beta8":
   287  			list = &appsbeta8
   288  		case "/apis/apps/v1beta9":
   289  			list = &appsbeta9
   290  		case "/apis/apps/v1beta10":
   291  			list = &appsbeta10
   292  		case "/api":
   293  			list = &metav1.APIVersions{
   294  				Versions: []string{
   295  					"v1",
   296  				},
   297  			}
   298  		case "/apis":
   299  			list = &metav1.APIGroupList{
   300  				Groups: []metav1.APIGroup{
   301  					{
   302  						Name: "apps",
   303  						Versions: []metav1.GroupVersionForDiscovery{
   304  							{GroupVersion: "apps/v1beta1", Version: "v1beta1"},
   305  							{GroupVersion: "apps/v1beta2", Version: "v1beta2"},
   306  							{GroupVersion: "apps/v1beta3", Version: "v1beta3"},
   307  							{GroupVersion: "apps/v1beta4", Version: "v1beta4"},
   308  							{GroupVersion: "apps/v1beta5", Version: "v1beta5"},
   309  							{GroupVersion: "apps/v1beta6", Version: "v1beta6"},
   310  							{GroupVersion: "apps/v1beta7", Version: "v1beta7"},
   311  							{GroupVersion: "apps/v1beta8", Version: "v1beta8"},
   312  							{GroupVersion: "apps/v1beta9", Version: "v1beta9"},
   313  							{GroupVersion: "apps/v1beta10", Version: "v1beta10"},
   314  						},
   315  					},
   316  					{
   317  						Name: "extensions",
   318  						Versions: []metav1.GroupVersionForDiscovery{
   319  							{GroupVersion: "extensions/v1beta1", Version: "v1beta1"},
   320  							{GroupVersion: "extensions/v1beta2", Version: "v1beta2"},
   321  							{GroupVersion: "extensions/v1beta3", Version: "v1beta3"},
   322  							{GroupVersion: "extensions/v1beta4", Version: "v1beta4"},
   323  							{GroupVersion: "extensions/v1beta5", Version: "v1beta5"},
   324  							{GroupVersion: "extensions/v1beta6", Version: "v1beta6"},
   325  							{GroupVersion: "extensions/v1beta7", Version: "v1beta7"},
   326  							{GroupVersion: "extensions/v1beta8", Version: "v1beta8"},
   327  							{GroupVersion: "extensions/v1beta9", Version: "v1beta9"},
   328  							{GroupVersion: "extensions/v1beta10", Version: "v1beta10"},
   329  						},
   330  					},
   331  				},
   332  			}
   333  		default:
   334  			t.Logf("unexpected request: %s", req.URL.Path)
   335  			w.WriteHeader(http.StatusNotFound)
   336  			return
   337  		}
   338  		output, err := json.Marshal(list)
   339  		if err != nil {
   340  			t.Errorf("unexpected encoding error: %v", err)
   341  			return
   342  		}
   343  		w.Header().Set("Content-Type", "application/json")
   344  		w.WriteHeader(http.StatusOK)
   345  		w.Write(output)
   346  	}))
   347  	defer server.Close()
   348  	for _, test := range tests {
   349  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   350  		got, err := client.ServerResourcesForGroupVersion(test.request)
   351  		if test.expectErr {
   352  			if err == nil {
   353  				t.Error("unexpected non-error")
   354  			}
   355  			continue
   356  		}
   357  		if err != nil {
   358  			t.Errorf("unexpected error: %v", err)
   359  			continue
   360  		}
   361  		if !reflect.DeepEqual(got, test.resourcesList) {
   362  			t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got)
   363  		}
   364  	}
   365  
   366  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   367  	start := time.Now()
   368  	serverResources, err := client.ServerResources()
   369  	if err != nil {
   370  		t.Errorf("unexpected error: %v", err)
   371  	}
   372  	end := time.Now()
   373  	if d := end.Sub(start); d > time.Second {
   374  		t.Errorf("took too long to perform discovery: %s", d)
   375  	}
   376  	serverGroupVersions := groupVersions(serverResources)
   377  	expectedGroupVersions := []string{
   378  		"v1",
   379  		"apps/v1beta1",
   380  		"apps/v1beta2",
   381  		"apps/v1beta3",
   382  		"apps/v1beta4",
   383  		"apps/v1beta5",
   384  		"apps/v1beta6",
   385  		"apps/v1beta7",
   386  		"apps/v1beta8",
   387  		"apps/v1beta9",
   388  		"apps/v1beta10",
   389  		"extensions/v1beta1",
   390  		"extensions/v1beta2",
   391  		"extensions/v1beta3",
   392  		"extensions/v1beta4",
   393  		"extensions/v1beta5",
   394  		"extensions/v1beta6",
   395  		"extensions/v1beta7",
   396  		"extensions/v1beta8",
   397  		"extensions/v1beta9",
   398  		"extensions/v1beta10",
   399  	}
   400  	if !reflect.DeepEqual(expectedGroupVersions, serverGroupVersions) {
   401  		t.Errorf("unexpected group versions: %v", diff.ObjectReflectDiff(expectedGroupVersions, serverGroupVersions))
   402  	}
   403  }
   404  
   405  func returnedOpenAPI() *openapi_v2.Document {
   406  	return &openapi_v2.Document{
   407  		Definitions: &openapi_v2.Definitions{
   408  			AdditionalProperties: []*openapi_v2.NamedSchema{
   409  				{
   410  					Name: "fake.type.1",
   411  					Value: &openapi_v2.Schema{
   412  						Properties: &openapi_v2.Properties{
   413  							AdditionalProperties: []*openapi_v2.NamedSchema{
   414  								{
   415  									Name: "count",
   416  									Value: &openapi_v2.Schema{
   417  										Type: &openapi_v2.TypeItem{
   418  											Value: []string{"integer"},
   419  										},
   420  									},
   421  								},
   422  							},
   423  						},
   424  					},
   425  				},
   426  				{
   427  					Name: "fake.type.2",
   428  					Value: &openapi_v2.Schema{
   429  						Properties: &openapi_v2.Properties{
   430  							AdditionalProperties: []*openapi_v2.NamedSchema{
   431  								{
   432  									Name: "count",
   433  									Value: &openapi_v2.Schema{
   434  										Type: &openapi_v2.TypeItem{
   435  											Value: []string{"array"},
   436  										},
   437  										Items: &openapi_v2.ItemsItem{
   438  											Schema: []*openapi_v2.Schema{
   439  												{
   440  													Type: &openapi_v2.TypeItem{
   441  														Value: []string{"string"},
   442  													},
   443  												},
   444  											},
   445  										},
   446  									},
   447  								},
   448  							},
   449  						},
   450  					},
   451  				},
   452  			},
   453  		},
   454  	}
   455  }
   456  
   457  func openapiSchemaDeprecatedFakeServer(status int, t *testing.T) (*httptest.Server, error) {
   458  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   459  		if req.URL.Path == "/openapi/v2" {
   460  			// write the error status for the new endpoint request
   461  			w.WriteHeader(status)
   462  			return
   463  		}
   464  		if req.URL.Path != "/swagger-2.0.0.pb-v1" {
   465  			errMsg := fmt.Sprintf("Unexpected url %v", req.URL)
   466  			w.WriteHeader(http.StatusNotFound)
   467  			w.Write([]byte(errMsg))
   468  			t.Errorf("testing should fail as %s", errMsg)
   469  			return
   470  		}
   471  		if req.Method != "GET" {
   472  			errMsg := fmt.Sprintf("Unexpected method %v", req.Method)
   473  			w.WriteHeader(http.StatusMethodNotAllowed)
   474  			w.Write([]byte(errMsg))
   475  			t.Errorf("testing should fail as %s", errMsg)
   476  			return
   477  		}
   478  
   479  		mime.AddExtensionType(".pb-v1", "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf")
   480  
   481  		output, err := proto.Marshal(returnedOpenAPI())
   482  		if err != nil {
   483  			errMsg := fmt.Sprintf("Unexpected marshal error: %v", err)
   484  			w.WriteHeader(http.StatusInternalServerError)
   485  			w.Write([]byte(errMsg))
   486  			t.Errorf("testing should fail as %s", errMsg)
   487  			return
   488  		}
   489  		w.WriteHeader(http.StatusOK)
   490  		w.Write(output)
   491  	}))
   492  
   493  	return server, nil
   494  }
   495  
   496  func openapiSchemaFakeServer(t *testing.T) (*httptest.Server, error) {
   497  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   498  		if req.URL.Path != "/openapi/v2" {
   499  			errMsg := fmt.Sprintf("Unexpected url %v", req.URL)
   500  			w.WriteHeader(http.StatusNotFound)
   501  			w.Write([]byte(errMsg))
   502  			t.Errorf("testing should fail as %s", errMsg)
   503  			return
   504  		}
   505  		if req.Method != "GET" {
   506  			errMsg := fmt.Sprintf("Unexpected method %v", req.Method)
   507  			w.WriteHeader(http.StatusMethodNotAllowed)
   508  			w.Write([]byte(errMsg))
   509  			t.Errorf("testing should fail as %s", errMsg)
   510  			return
   511  		}
   512  		decipherableFormat := req.Header.Get("Accept")
   513  		if decipherableFormat != "application/com.github.proto-openapi.spec.v2@v1.0+protobuf" {
   514  			errMsg := fmt.Sprintf("Unexpected accept mime type %v", decipherableFormat)
   515  			w.WriteHeader(http.StatusUnsupportedMediaType)
   516  			w.Write([]byte(errMsg))
   517  			t.Errorf("testing should fail as %s", errMsg)
   518  			return
   519  		}
   520  
   521  		mime.AddExtensionType(".pb-v1", "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf")
   522  
   523  		output, err := proto.Marshal(returnedOpenAPI())
   524  		if err != nil {
   525  			errMsg := fmt.Sprintf("Unexpected marshal error: %v", err)
   526  			w.WriteHeader(http.StatusInternalServerError)
   527  			w.Write([]byte(errMsg))
   528  			t.Errorf("testing should fail as %s", errMsg)
   529  			return
   530  		}
   531  		w.WriteHeader(http.StatusOK)
   532  		w.Write(output)
   533  	}))
   534  
   535  	return server, nil
   536  }
   537  
   538  func TestGetOpenAPISchema(t *testing.T) {
   539  	server, err := openapiSchemaFakeServer(t)
   540  	if err != nil {
   541  		t.Errorf("unexpected error starting fake server: %v", err)
   542  	}
   543  	defer server.Close()
   544  
   545  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   546  	got, err := client.OpenAPISchema()
   547  	if err != nil {
   548  		t.Fatalf("unexpected error getting openapi: %v", err)
   549  	}
   550  	if e, a := returnedOpenAPI(), got; !golangproto.Equal(e, a) {
   551  		t.Errorf("expected \n%v, got \n%v", e, a)
   552  	}
   553  }
   554  
   555  func TestGetOpenAPISchemaForbiddenFallback(t *testing.T) {
   556  	server, err := openapiSchemaDeprecatedFakeServer(http.StatusForbidden, t)
   557  	if err != nil {
   558  		t.Errorf("unexpected error starting fake server: %v", err)
   559  	}
   560  	defer server.Close()
   561  
   562  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   563  	got, err := client.OpenAPISchema()
   564  	if err != nil {
   565  		t.Fatalf("unexpected error getting openapi: %v", err)
   566  	}
   567  	if e, a := returnedOpenAPI(), got; !golangproto.Equal(e, a) {
   568  		t.Errorf("expected %v, got %v", e, a)
   569  	}
   570  }
   571  
   572  func TestGetOpenAPISchemaNotFoundFallback(t *testing.T) {
   573  	server, err := openapiSchemaDeprecatedFakeServer(http.StatusNotFound, t)
   574  	if err != nil {
   575  		t.Errorf("unexpected error starting fake server: %v", err)
   576  	}
   577  	defer server.Close()
   578  
   579  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   580  	got, err := client.OpenAPISchema()
   581  	if err != nil {
   582  		t.Fatalf("unexpected error getting openapi: %v", err)
   583  	}
   584  	if e, a := returnedOpenAPI(), got; !golangproto.Equal(e, a) {
   585  		t.Errorf("expected %v, got %v", e, a)
   586  	}
   587  }
   588  
   589  func TestGetOpenAPISchemaNotAcceptableFallback(t *testing.T) {
   590  	server, err := openapiSchemaDeprecatedFakeServer(http.StatusNotAcceptable, t)
   591  	if err != nil {
   592  		t.Errorf("unexpected error starting fake server: %v", err)
   593  	}
   594  	defer server.Close()
   595  
   596  	client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   597  	got, err := client.OpenAPISchema()
   598  	if err != nil {
   599  		t.Fatalf("unexpected error getting openapi: %v", err)
   600  	}
   601  	if e, a := returnedOpenAPI(), got; !golangproto.Equal(e, a) {
   602  		t.Errorf("expected %v, got %v", e, a)
   603  	}
   604  }
   605  
   606  func TestServerPreferredResources(t *testing.T) {
   607  	stable := metav1.APIResourceList{
   608  		GroupVersion: "v1",
   609  		APIResources: []metav1.APIResource{
   610  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   611  			{Name: "services", Namespaced: true, Kind: "Service"},
   612  			{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
   613  		},
   614  	}
   615  	tests := []struct {
   616  		resourcesList []*metav1.APIResourceList
   617  		response      func(w http.ResponseWriter, req *http.Request)
   618  		expectErr     func(err error) bool
   619  	}{
   620  		{
   621  			resourcesList: []*metav1.APIResourceList{&stable},
   622  			expectErr:     IsGroupDiscoveryFailedError,
   623  			response: func(w http.ResponseWriter, req *http.Request) {
   624  				var list interface{}
   625  				switch req.URL.Path {
   626  				case "/apis/extensions/v1beta1":
   627  					w.WriteHeader(http.StatusInternalServerError)
   628  					return
   629  				case "/api/v1":
   630  					list = &stable
   631  				case "/api":
   632  					list = &metav1.APIVersions{
   633  						Versions: []string{
   634  							"v1",
   635  						},
   636  					}
   637  				case "/apis":
   638  					list = &metav1.APIGroupList{
   639  						Groups: []metav1.APIGroup{
   640  							{
   641  								Versions: []metav1.GroupVersionForDiscovery{
   642  									{GroupVersion: "extensions/v1beta1"},
   643  								},
   644  							},
   645  						},
   646  					}
   647  				default:
   648  					t.Logf("unexpected request: %s", req.URL.Path)
   649  					w.WriteHeader(http.StatusNotFound)
   650  					return
   651  				}
   652  				output, err := json.Marshal(list)
   653  				if err != nil {
   654  					t.Errorf("unexpected encoding error: %v", err)
   655  					return
   656  				}
   657  				w.Header().Set("Content-Type", "application/json")
   658  				w.WriteHeader(http.StatusOK)
   659  				w.Write(output)
   660  			},
   661  		},
   662  		{
   663  			resourcesList: nil,
   664  			expectErr:     IsGroupDiscoveryFailedError,
   665  			response: func(w http.ResponseWriter, req *http.Request) {
   666  				var list interface{}
   667  				switch req.URL.Path {
   668  				case "/apis/extensions/v1beta1":
   669  					w.WriteHeader(http.StatusInternalServerError)
   670  					return
   671  				case "/api/v1":
   672  					w.WriteHeader(http.StatusInternalServerError)
   673  				case "/api":
   674  					list = &metav1.APIVersions{
   675  						Versions: []string{
   676  							"v1",
   677  						},
   678  					}
   679  				case "/apis":
   680  					list = &metav1.APIGroupList{
   681  						Groups: []metav1.APIGroup{
   682  							{
   683  								Versions: []metav1.GroupVersionForDiscovery{
   684  									{GroupVersion: "extensions/v1beta1"},
   685  								},
   686  							},
   687  						},
   688  					}
   689  				default:
   690  					t.Logf("unexpected request: %s", req.URL.Path)
   691  					w.WriteHeader(http.StatusNotFound)
   692  					return
   693  				}
   694  				output, err := json.Marshal(list)
   695  				if err != nil {
   696  					t.Errorf("unexpected encoding error: %v", err)
   697  					return
   698  				}
   699  				w.Header().Set("Content-Type", "application/json")
   700  				w.WriteHeader(http.StatusOK)
   701  				w.Write(output)
   702  			},
   703  		},
   704  	}
   705  	for _, test := range tests {
   706  		server := httptest.NewServer(http.HandlerFunc(test.response))
   707  		defer server.Close()
   708  
   709  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   710  		resources, err := client.ServerPreferredResources()
   711  		if test.expectErr != nil {
   712  			if err == nil {
   713  				t.Error("unexpected non-error")
   714  			}
   715  
   716  			continue
   717  		}
   718  		if err != nil {
   719  			t.Errorf("unexpected error: %v", err)
   720  			continue
   721  		}
   722  		got, err := GroupVersionResources(resources)
   723  		if err != nil {
   724  			t.Errorf("unexpected error: %v", err)
   725  			continue
   726  		}
   727  		expected, _ := GroupVersionResources(test.resourcesList)
   728  		if !reflect.DeepEqual(got, expected) {
   729  			t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got)
   730  		}
   731  		server.Close()
   732  	}
   733  }
   734  
   735  func TestServerPreferredResourcesRetries(t *testing.T) {
   736  	stable := metav1.APIResourceList{
   737  		GroupVersion: "v1",
   738  		APIResources: []metav1.APIResource{
   739  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   740  		},
   741  	}
   742  	beta := metav1.APIResourceList{
   743  		GroupVersion: "extensions/v1",
   744  		APIResources: []metav1.APIResource{
   745  			{Name: "deployments", Namespaced: true, Kind: "Deployment"},
   746  		},
   747  	}
   748  
   749  	response := func(numErrors int) http.HandlerFunc {
   750  		var i = 0
   751  		return func(w http.ResponseWriter, req *http.Request) {
   752  			var list interface{}
   753  			switch req.URL.Path {
   754  			case "/apis/extensions/v1beta1":
   755  				if i < numErrors {
   756  					i++
   757  					w.WriteHeader(http.StatusInternalServerError)
   758  					return
   759  				}
   760  				list = &beta
   761  			case "/api/v1":
   762  				list = &stable
   763  			case "/api":
   764  				list = &metav1.APIVersions{
   765  					Versions: []string{
   766  						"v1",
   767  					},
   768  				}
   769  			case "/apis":
   770  				list = &metav1.APIGroupList{
   771  					Groups: []metav1.APIGroup{
   772  						{
   773  							Name: "extensions",
   774  							Versions: []metav1.GroupVersionForDiscovery{
   775  								{GroupVersion: "extensions/v1beta1", Version: "v1beta1"},
   776  							},
   777  							PreferredVersion: metav1.GroupVersionForDiscovery{
   778  								GroupVersion: "extensions/v1beta1",
   779  								Version:      "v1beta1",
   780  							},
   781  						},
   782  					},
   783  				}
   784  			default:
   785  				t.Logf("unexpected request: %s", req.URL.Path)
   786  				w.WriteHeader(http.StatusNotFound)
   787  				return
   788  			}
   789  			output, err := json.Marshal(list)
   790  			if err != nil {
   791  				t.Errorf("unexpected encoding error: %v", err)
   792  				return
   793  			}
   794  			w.Header().Set("Content-Type", "application/json")
   795  			w.WriteHeader(http.StatusOK)
   796  			w.Write(output)
   797  		}
   798  	}
   799  	tests := []struct {
   800  		responseErrors  int
   801  		expectResources int
   802  		expectedError   func(err error) bool
   803  	}{
   804  		{
   805  			responseErrors:  1,
   806  			expectResources: 2,
   807  			expectedError: func(err error) bool {
   808  				return err == nil
   809  			},
   810  		},
   811  		{
   812  			responseErrors:  2,
   813  			expectResources: 1,
   814  			expectedError:   IsGroupDiscoveryFailedError,
   815  		},
   816  	}
   817  
   818  	for i, tc := range tests {
   819  		server := httptest.NewServer(http.HandlerFunc(response(tc.responseErrors)))
   820  		defer server.Close()
   821  
   822  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   823  		resources, err := client.ServerPreferredResources()
   824  		if !tc.expectedError(err) {
   825  			t.Errorf("case %d: unexpected error: %v", i, err)
   826  		}
   827  		got, err := GroupVersionResources(resources)
   828  		if err != nil {
   829  			t.Errorf("case %d: unexpected error: %v", i, err)
   830  		}
   831  		if len(got) != tc.expectResources {
   832  			t.Errorf("case %d: expect %d resources, got %#v", i, tc.expectResources, got)
   833  		}
   834  		server.Close()
   835  	}
   836  }
   837  
   838  func TestServerPreferredNamespacedResources(t *testing.T) {
   839  	stable := metav1.APIResourceList{
   840  		GroupVersion: "v1",
   841  		APIResources: []metav1.APIResource{
   842  			{Name: "pods", Namespaced: true, Kind: "Pod"},
   843  			{Name: "services", Namespaced: true, Kind: "Service"},
   844  			{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
   845  		},
   846  	}
   847  	batchv1 := metav1.APIResourceList{
   848  		GroupVersion: "batch/v1",
   849  		APIResources: []metav1.APIResource{
   850  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   851  		},
   852  	}
   853  	batchv2alpha1 := metav1.APIResourceList{
   854  		GroupVersion: "batch/v2alpha1",
   855  		APIResources: []metav1.APIResource{
   856  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   857  			{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
   858  		},
   859  	}
   860  	batchv3alpha1 := metav1.APIResourceList{
   861  		GroupVersion: "batch/v3alpha1",
   862  		APIResources: []metav1.APIResource{
   863  			{Name: "jobs", Namespaced: true, Kind: "Job"},
   864  			{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
   865  		},
   866  	}
   867  	tests := []struct {
   868  		response func(w http.ResponseWriter, req *http.Request)
   869  		expected map[schema.GroupVersionResource]struct{}
   870  	}{
   871  		{
   872  			response: func(w http.ResponseWriter, req *http.Request) {
   873  				var list interface{}
   874  				switch req.URL.Path {
   875  				case "/api/v1":
   876  					list = &stable
   877  				case "/api":
   878  					list = &metav1.APIVersions{
   879  						Versions: []string{
   880  							"v1",
   881  						},
   882  					}
   883  				default:
   884  					t.Logf("unexpected request: %s", req.URL.Path)
   885  					w.WriteHeader(http.StatusNotFound)
   886  					return
   887  				}
   888  				output, err := json.Marshal(list)
   889  				if err != nil {
   890  					t.Errorf("unexpected encoding error: %v", err)
   891  					return
   892  				}
   893  				w.Header().Set("Content-Type", "application/json")
   894  				w.WriteHeader(http.StatusOK)
   895  				w.Write(output)
   896  			},
   897  			expected: map[schema.GroupVersionResource]struct{}{
   898  				{Group: "", Version: "v1", Resource: "pods"}:     {},
   899  				{Group: "", Version: "v1", Resource: "services"}: {},
   900  			},
   901  		},
   902  		{
   903  			response: func(w http.ResponseWriter, req *http.Request) {
   904  				var list interface{}
   905  				switch req.URL.Path {
   906  				case "/apis":
   907  					list = &metav1.APIGroupList{
   908  						Groups: []metav1.APIGroup{
   909  							{
   910  								Name: "batch",
   911  								Versions: []metav1.GroupVersionForDiscovery{
   912  									{GroupVersion: "batch/v1", Version: "v1"},
   913  									{GroupVersion: "batch/v2alpha1", Version: "v2alpha1"},
   914  									{GroupVersion: "batch/v3alpha1", Version: "v3alpha1"},
   915  								},
   916  								PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v1", Version: "v1"},
   917  							},
   918  						},
   919  					}
   920  				case "/apis/batch/v1":
   921  					list = &batchv1
   922  				case "/apis/batch/v2alpha1":
   923  					list = &batchv2alpha1
   924  				case "/apis/batch/v3alpha1":
   925  					list = &batchv3alpha1
   926  				default:
   927  					t.Logf("unexpected request: %s", req.URL.Path)
   928  					w.WriteHeader(http.StatusNotFound)
   929  					return
   930  				}
   931  				output, err := json.Marshal(list)
   932  				if err != nil {
   933  					t.Errorf("unexpected encoding error: %v", err)
   934  					return
   935  				}
   936  				w.Header().Set("Content-Type", "application/json")
   937  				w.WriteHeader(http.StatusOK)
   938  				w.Write(output)
   939  			},
   940  			expected: map[schema.GroupVersionResource]struct{}{
   941  				{Group: "batch", Version: "v1", Resource: "jobs"}:           {},
   942  				{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
   943  			},
   944  		},
   945  		{
   946  			response: func(w http.ResponseWriter, req *http.Request) {
   947  				var list interface{}
   948  				switch req.URL.Path {
   949  				case "/apis":
   950  					list = &metav1.APIGroupList{
   951  						Groups: []metav1.APIGroup{
   952  							{
   953  								Name: "batch",
   954  								Versions: []metav1.GroupVersionForDiscovery{
   955  									{GroupVersion: "batch/v1", Version: "v1"},
   956  									{GroupVersion: "batch/v2alpha1", Version: "v2alpha1"},
   957  									{GroupVersion: "batch/v3alpha1", Version: "v3alpha1"},
   958  								},
   959  								PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v2alpha", Version: "v2alpha1"},
   960  							},
   961  						},
   962  					}
   963  				case "/apis/batch/v1":
   964  					list = &batchv1
   965  				case "/apis/batch/v2alpha1":
   966  					list = &batchv2alpha1
   967  				case "/apis/batch/v3alpha1":
   968  					list = &batchv3alpha1
   969  				default:
   970  					t.Logf("unexpected request: %s", req.URL.Path)
   971  					w.WriteHeader(http.StatusNotFound)
   972  					return
   973  				}
   974  				output, err := json.Marshal(list)
   975  				if err != nil {
   976  					t.Errorf("unexpected encoding error: %v", err)
   977  					return
   978  				}
   979  				w.Header().Set("Content-Type", "application/json")
   980  				w.WriteHeader(http.StatusOK)
   981  				w.Write(output)
   982  			},
   983  			expected: map[schema.GroupVersionResource]struct{}{
   984  				{Group: "batch", Version: "v2alpha1", Resource: "jobs"}:     {},
   985  				{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
   986  			},
   987  		},
   988  	}
   989  	for i, test := range tests {
   990  		server := httptest.NewServer(http.HandlerFunc(test.response))
   991  		defer server.Close()
   992  
   993  		client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
   994  		resources, err := client.ServerPreferredNamespacedResources()
   995  		if err != nil {
   996  			t.Errorf("[%d] unexpected error: %v", i, err)
   997  			continue
   998  		}
   999  		got, err := GroupVersionResources(resources)
  1000  		if err != nil {
  1001  			t.Errorf("[%d] unexpected error: %v", i, err)
  1002  			continue
  1003  		}
  1004  
  1005  		if !reflect.DeepEqual(got, test.expected) {
  1006  			t.Errorf("[%d] expected:\n%v\ngot:\n%v\n", i, test.expected, got)
  1007  		}
  1008  		server.Close()
  1009  	}
  1010  }
  1011  
  1012  func groupVersions(resources []*metav1.APIResourceList) []string {
  1013  	result := []string{}
  1014  	for _, resourceList := range resources {
  1015  		result = append(result, resourceList.GroupVersion)
  1016  	}
  1017  	return result
  1018  }