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 }