k8s.io/client-go@v0.31.1/discovery/cached/memory/memcache_test.go (about) 1 /* 2 Copyright 2017 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 memory 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "net/http" 24 "net/http/httptest" 25 "reflect" 26 "sync" 27 "testing" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 apidiscovery "k8s.io/api/apidiscovery/v2" 32 errorsutil "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/util/sets" 37 "k8s.io/client-go/discovery" 38 "k8s.io/client-go/discovery/fake" 39 "k8s.io/client-go/openapi" 40 "k8s.io/client-go/rest" 41 testutil "k8s.io/client-go/util/testing" 42 ) 43 44 type resourceMapEntry struct { 45 list *metav1.APIResourceList 46 err error 47 } 48 49 type fakeDiscovery struct { 50 *fake.FakeDiscovery 51 52 lock sync.Mutex 53 groupList *metav1.APIGroupList 54 groupListErr error 55 resourceMap map[string]*resourceMapEntry 56 } 57 58 func (c *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) { 59 c.lock.Lock() 60 defer c.lock.Unlock() 61 if rl, ok := c.resourceMap[groupVersion]; ok { 62 return rl.list, rl.err 63 } 64 return nil, errors.New("doesn't exist") 65 } 66 67 func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) { 68 c.lock.Lock() 69 defer c.lock.Unlock() 70 if c.groupList == nil { 71 return nil, errors.New("doesn't exist") 72 } 73 return c.groupList, c.groupListErr 74 } 75 76 func TestClient(t *testing.T) { 77 fake := &fakeDiscovery{ 78 groupList: &metav1.APIGroupList{ 79 Groups: []metav1.APIGroup{{ 80 Name: "astronomy", 81 Versions: []metav1.GroupVersionForDiscovery{{ 82 GroupVersion: "astronomy/v8beta1", 83 Version: "v8beta1", 84 }}, 85 }}, 86 }, 87 resourceMap: map[string]*resourceMapEntry{ 88 "astronomy/v8beta1": { 89 list: &metav1.APIResourceList{ 90 GroupVersion: "astronomy/v8beta1", 91 APIResources: []metav1.APIResource{{ 92 Name: "dwarfplanets", 93 SingularName: "dwarfplanet", 94 Namespaced: true, 95 Kind: "DwarfPlanet", 96 ShortNames: []string{"dp"}, 97 }}, 98 }, 99 }, 100 }, 101 } 102 103 c := NewMemCacheClient(fake) 104 if c.Fresh() { 105 t.Errorf("Expected not fresh.") 106 } 107 g, err := c.ServerGroups() 108 if err != nil { 109 t.Errorf("Unexpected error: %v", err) 110 } 111 if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) { 112 t.Errorf("Expected %#v, got %#v", e, a) 113 } 114 if !c.Fresh() { 115 t.Errorf("Expected fresh.") 116 } 117 c.Invalidate() 118 if c.Fresh() { 119 t.Errorf("Expected not fresh.") 120 } 121 122 g, err = c.ServerGroups() 123 if err != nil { 124 t.Errorf("Unexpected error: %v", err) 125 } 126 if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) { 127 t.Errorf("Expected %#v, got %#v", e, a) 128 } 129 if !c.Fresh() { 130 t.Errorf("Expected fresh.") 131 } 132 r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1") 133 if err != nil { 134 t.Errorf("Unexpected error: %v", err) 135 } 136 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 137 t.Errorf("Expected %#v, got %#v", e, a) 138 } 139 140 fake.lock.Lock() 141 fake.resourceMap = map[string]*resourceMapEntry{ 142 "astronomy/v8beta1": { 143 list: &metav1.APIResourceList{ 144 GroupVersion: "astronomy/v8beta1", 145 APIResources: []metav1.APIResource{{ 146 Name: "stars", 147 SingularName: "star", 148 Namespaced: true, 149 Kind: "Star", 150 ShortNames: []string{"s"}, 151 }}, 152 }, 153 }, 154 } 155 fake.lock.Unlock() 156 157 c.Invalidate() 158 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 159 if err != nil { 160 t.Errorf("Unexpected error: %v", err) 161 } 162 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 163 t.Errorf("Expected %#v, got %#v", e, a) 164 } 165 } 166 167 func TestServerGroupsFails(t *testing.T) { 168 fake := &fakeDiscovery{ 169 groupList: &metav1.APIGroupList{ 170 Groups: []metav1.APIGroup{{ 171 Name: "astronomy", 172 Versions: []metav1.GroupVersionForDiscovery{{ 173 GroupVersion: "astronomy/v8beta1", 174 Version: "v8beta1", 175 }}, 176 }}, 177 }, 178 groupListErr: errors.New("some error"), 179 resourceMap: map[string]*resourceMapEntry{ 180 "astronomy/v8beta1": { 181 list: &metav1.APIResourceList{ 182 GroupVersion: "astronomy/v8beta1", 183 APIResources: []metav1.APIResource{{ 184 Name: "dwarfplanets", 185 SingularName: "dwarfplanet", 186 Namespaced: true, 187 Kind: "DwarfPlanet", 188 ShortNames: []string{"dp"}, 189 }}, 190 }, 191 }, 192 }, 193 } 194 195 c := NewMemCacheClient(fake) 196 if c.Fresh() { 197 t.Errorf("Expected not fresh.") 198 } 199 _, err := c.ServerGroups() 200 if err == nil { 201 t.Errorf("Expected error") 202 } 203 if c.Fresh() { 204 t.Errorf("Expected not fresh.") 205 } 206 fake.lock.Lock() 207 fake.groupListErr = nil 208 fake.lock.Unlock() 209 r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1") 210 if err != nil { 211 t.Errorf("Unexpected error: %v", err) 212 } 213 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 214 t.Errorf("Expected %#v, got %#v", e, a) 215 } 216 if !c.Fresh() { 217 t.Errorf("Expected not fresh.") 218 } 219 } 220 221 func TestPartialPermanentFailure(t *testing.T) { 222 fake := &fakeDiscovery{ 223 groupList: &metav1.APIGroupList{ 224 Groups: []metav1.APIGroup{ 225 { 226 Name: "astronomy", 227 Versions: []metav1.GroupVersionForDiscovery{{ 228 GroupVersion: "astronomy/v8beta1", 229 Version: "v8beta1", 230 }}, 231 }, 232 { 233 Name: "astronomy2", 234 Versions: []metav1.GroupVersionForDiscovery{{ 235 GroupVersion: "astronomy2/v8beta1", 236 Version: "v8beta1", 237 }}, 238 }, 239 }, 240 }, 241 resourceMap: map[string]*resourceMapEntry{ 242 "astronomy/v8beta1": { 243 err: errors.New("some permanent error"), 244 }, 245 "astronomy2/v8beta1": { 246 list: &metav1.APIResourceList{ 247 GroupVersion: "astronomy2/v8beta1", 248 APIResources: []metav1.APIResource{{ 249 Name: "dwarfplanets", 250 SingularName: "dwarfplanet", 251 Namespaced: true, 252 Kind: "DwarfPlanet", 253 ShortNames: []string{"dp"}, 254 }}, 255 }, 256 }, 257 }, 258 } 259 260 c := NewMemCacheClient(fake) 261 if c.Fresh() { 262 t.Errorf("Expected not fresh.") 263 } 264 r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1") 265 if err != nil { 266 t.Errorf("Unexpected error: %v", err) 267 } 268 if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 269 t.Errorf("Expected %#v, got %#v", e, a) 270 } 271 _, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 272 if err == nil { 273 t.Errorf("Expected error, got nil") 274 } 275 276 fake.lock.Lock() 277 fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{ 278 list: &metav1.APIResourceList{ 279 GroupVersion: "astronomy/v8beta1", 280 APIResources: []metav1.APIResource{{ 281 Name: "dwarfplanets", 282 SingularName: "dwarfplanet", 283 Namespaced: true, 284 Kind: "DwarfPlanet", 285 ShortNames: []string{"dp"}, 286 }}, 287 }, 288 err: nil, 289 } 290 fake.lock.Unlock() 291 // We don't retry permanent errors, so it should fail. 292 _, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 293 if err == nil { 294 t.Errorf("Expected error, got nil") 295 } 296 c.Invalidate() 297 298 // After Invalidate, we should retry. 299 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 300 if err != nil { 301 t.Errorf("Unexpected error: %v", err) 302 } 303 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 304 t.Errorf("Expected %#v, got %#v", e, a) 305 } 306 } 307 308 func TestPartialRetryableFailure(t *testing.T) { 309 fake := &fakeDiscovery{ 310 groupList: &metav1.APIGroupList{ 311 Groups: []metav1.APIGroup{ 312 { 313 Name: "astronomy", 314 Versions: []metav1.GroupVersionForDiscovery{{ 315 GroupVersion: "astronomy/v8beta1", 316 Version: "v8beta1", 317 }}, 318 }, 319 { 320 Name: "astronomy2", 321 Versions: []metav1.GroupVersionForDiscovery{{ 322 GroupVersion: "astronomy2/v8beta1", 323 Version: "v8beta1", 324 }}, 325 }, 326 }, 327 }, 328 resourceMap: map[string]*resourceMapEntry{ 329 "astronomy/v8beta1": { 330 err: &errorsutil.StatusError{ 331 ErrStatus: metav1.Status{ 332 Message: "Some retryable error", 333 Code: int32(http.StatusServiceUnavailable), 334 Reason: metav1.StatusReasonServiceUnavailable, 335 }, 336 }, 337 }, 338 "astronomy2/v8beta1": { 339 list: &metav1.APIResourceList{ 340 GroupVersion: "astronomy2/v8beta1", 341 APIResources: []metav1.APIResource{{ 342 Name: "dwarfplanets", 343 SingularName: "dwarfplanet", 344 Namespaced: true, 345 Kind: "DwarfPlanet", 346 ShortNames: []string{"dp"}, 347 }}, 348 }, 349 }, 350 }, 351 } 352 353 c := NewMemCacheClient(fake) 354 if c.Fresh() { 355 t.Errorf("Expected not fresh.") 356 } 357 r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1") 358 if err != nil { 359 t.Errorf("Unexpected error: %v", err) 360 } 361 if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 362 t.Errorf("Expected %#v, got %#v", e, a) 363 } 364 _, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 365 if err == nil { 366 t.Errorf("Expected error, got nil") 367 } 368 369 fake.lock.Lock() 370 fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{ 371 list: &metav1.APIResourceList{ 372 GroupVersion: "astronomy/v8beta1", 373 APIResources: []metav1.APIResource{{ 374 Name: "dwarfplanets", 375 SingularName: "dwarfplanet", 376 Namespaced: true, 377 Kind: "DwarfPlanet", 378 ShortNames: []string{"dp"}, 379 }}, 380 }, 381 err: nil, 382 } 383 fake.lock.Unlock() 384 // We should retry retryable error even without Invalidate() being called, 385 // so no error is expected. 386 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 387 if err != nil { 388 t.Errorf("Expected no error, got %v", err) 389 } 390 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 391 t.Errorf("Expected %#v, got %#v", e, a) 392 } 393 394 // Check that the last result was cached and we don't retry further. 395 fake.lock.Lock() 396 fake.resourceMap["astronomy/v8beta1"].err = errors.New("some permanent error") 397 fake.lock.Unlock() 398 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1") 399 if err != nil { 400 t.Errorf("Expected no error, got %v", err) 401 } 402 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) { 403 t.Errorf("Expected %#v, got %#v", e, a) 404 } 405 } 406 407 // Tests that schema instances returned by openapi cached and returned after 408 // successive calls 409 func TestOpenAPIMemCache(t *testing.T) { 410 fakeServer, err := testutil.NewFakeOpenAPIV3Server("../../testdata") 411 require.NoError(t, err) 412 defer fakeServer.HttpServer.Close() 413 414 require.NotEmpty(t, fakeServer.ServedDocuments) 415 416 client := NewMemCacheClient( 417 discovery.NewDiscoveryClientForConfigOrDie( 418 &rest.Config{Host: fakeServer.HttpServer.URL}, 419 ), 420 ) 421 openapiClient := client.OpenAPIV3() 422 423 paths, err := openapiClient.Paths() 424 require.NoError(t, err) 425 426 contentTypes := []string{ 427 runtime.ContentTypeJSON, openapi.ContentTypeOpenAPIV3PB, 428 } 429 430 for _, contentType := range contentTypes { 431 t.Run(contentType, func(t *testing.T) { 432 for k, v := range paths { 433 original, err := v.Schema(contentType) 434 if !assert.NoError(t, err) { 435 continue 436 } 437 438 pathsAgain, err := openapiClient.Paths() 439 if !assert.NoError(t, err) { 440 continue 441 } 442 443 schemaAgain, err := pathsAgain[k].Schema(contentType) 444 if !assert.NoError(t, err) { 445 continue 446 } 447 448 assert.True(t, reflect.ValueOf(paths).Pointer() == reflect.ValueOf(pathsAgain).Pointer()) 449 assert.True(t, reflect.ValueOf(original).Pointer() == reflect.ValueOf(schemaAgain).Pointer()) 450 451 // Invalidate and try again. This time pointers should not be equal 452 client.Invalidate() 453 454 pathsAgain, err = client.OpenAPIV3().Paths() 455 if !assert.NoError(t, err) { 456 continue 457 } 458 459 schemaAgain, err = pathsAgain[k].Schema(contentType) 460 if !assert.NoError(t, err) { 461 continue 462 } 463 464 assert.True(t, reflect.ValueOf(paths).Pointer() != reflect.ValueOf(pathsAgain).Pointer()) 465 assert.True(t, reflect.ValueOf(original).Pointer() != reflect.ValueOf(schemaAgain).Pointer()) 466 assert.Equal(t, original, schemaAgain) 467 } 468 }) 469 } 470 } 471 472 // Tests function "GroupsAndMaybeResources" when the "unaggregated" discovery is returned. 473 func TestMemCacheGroupsAndMaybeResources(t *testing.T) { 474 tests := []struct { 475 name string 476 corev1 *metav1.APIVersions 477 apis *metav1.APIGroupList 478 expectedGroupNames []string 479 expectedGroupVersions []string 480 }{ 481 { 482 name: "Legacy discovery format: 1 version at /api, 1 group at /apis", 483 corev1: &metav1.APIVersions{ 484 Versions: []string{ 485 "v1", 486 }, 487 }, 488 apis: &metav1.APIGroupList{ 489 Groups: []metav1.APIGroup{ 490 { 491 Name: "extensions", 492 Versions: []metav1.GroupVersionForDiscovery{ 493 {GroupVersion: "extensions/v1beta1"}, 494 }, 495 }, 496 }, 497 }, 498 expectedGroupNames: []string{"", "extensions"}, 499 expectedGroupVersions: []string{"v1", "extensions/v1beta1"}, 500 }, 501 { 502 name: "Legacy discovery format: 1 version at /api, 2 groups/1 version at /apis", 503 corev1: &metav1.APIVersions{ 504 Versions: []string{ 505 "v1", 506 }, 507 }, 508 apis: &metav1.APIGroupList{ 509 Groups: []metav1.APIGroup{ 510 { 511 Name: "apps", 512 Versions: []metav1.GroupVersionForDiscovery{ 513 {GroupVersion: "apps/v1"}, 514 }, 515 }, 516 { 517 Name: "extensions", 518 Versions: []metav1.GroupVersionForDiscovery{ 519 {GroupVersion: "extensions/v1beta1"}, 520 }, 521 }, 522 }, 523 }, 524 expectedGroupNames: []string{"", "apps", "extensions"}, 525 expectedGroupVersions: []string{"v1", "apps/v1", "extensions/v1beta1"}, 526 }, 527 { 528 name: "Legacy discovery format: 1 version at /api, 2 groups/2 versions at /apis", 529 corev1: &metav1.APIVersions{ 530 Versions: []string{ 531 "v1", 532 }, 533 }, 534 apis: &metav1.APIGroupList{ 535 Groups: []metav1.APIGroup{ 536 { 537 Name: "batch", 538 Versions: []metav1.GroupVersionForDiscovery{ 539 {GroupVersion: "batch/v1"}, 540 }, 541 }, 542 { 543 Name: "batch", 544 Versions: []metav1.GroupVersionForDiscovery{ 545 {GroupVersion: "batch/v1beta1"}, 546 }, 547 }, 548 { 549 Name: "extensions", 550 Versions: []metav1.GroupVersionForDiscovery{ 551 {GroupVersion: "extensions/v1beta1"}, 552 }, 553 }, 554 { 555 Name: "extensions", 556 Versions: []metav1.GroupVersionForDiscovery{ 557 {GroupVersion: "extensions/v1alpha1"}, 558 }, 559 }, 560 }, 561 }, 562 expectedGroupNames: []string{ 563 "", 564 "batch", 565 "extensions", 566 }, 567 expectedGroupVersions: []string{ 568 "v1", 569 "batch/v1", 570 "batch/v1beta1", 571 "extensions/v1beta1", 572 "extensions/v1alpha1", 573 }, 574 }, 575 } 576 577 for _, test := range tests { 578 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 579 var body interface{} 580 switch req.URL.Path { 581 case "/api": 582 body = test.corev1 583 case "/apis": 584 body = test.apis 585 default: 586 w.WriteHeader(http.StatusNotFound) 587 return 588 } 589 output, err := json.Marshal(body) 590 require.NoError(t, err) 591 // Content-type is "unaggregated" discovery format -- no resources returned. 592 w.Header().Set("Content-Type", discovery.AcceptV1) 593 w.WriteHeader(http.StatusOK) 594 w.Write(output) 595 })) 596 defer server.Close() 597 client := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: server.URL}) 598 memClient := memCacheClient{ 599 delegate: client, 600 groupToServerResources: map[string]*cacheEntry{}, 601 } 602 assert.False(t, memClient.Fresh()) 603 apiGroupList, resourcesMap, failedGVs, err := memClient.GroupsAndMaybeResources() 604 require.NoError(t, err) 605 // "Unaggregated" discovery always returns nil for resources. 606 assert.Nil(t, resourcesMap) 607 assert.Emptyf(t, failedGVs, "expected empty failed GroupVersions, got (%d)", len(failedGVs)) 608 assert.False(t, memClient.receivedAggregatedDiscovery) 609 assert.True(t, memClient.Fresh()) 610 // Test the expected groups are returned for the aggregated format. 611 expectedGroupNames := sets.NewString(test.expectedGroupNames...) 612 actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...) 613 assert.True(t, expectedGroupNames.Equal(actualGroupNames), 614 "%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List()) 615 // Test the expected group versions for the aggregated discovery is correct. 616 expectedGroupVersions := sets.NewString(test.expectedGroupVersions...) 617 actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...) 618 assert.True(t, expectedGroupVersions.Equal(actualGroupVersions), 619 "%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List()) 620 // Invalidate the cache and retrieve the server groups and resources again. 621 memClient.Invalidate() 622 assert.False(t, memClient.Fresh()) 623 apiGroupList, resourcesMap, _, err = memClient.GroupsAndMaybeResources() 624 require.NoError(t, err) 625 assert.Nil(t, resourcesMap) 626 assert.False(t, memClient.receivedAggregatedDiscovery) 627 // Test the expected groups are returned for the aggregated format. 628 actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...) 629 assert.True(t, expectedGroupNames.Equal(actualGroupNames), 630 "%s: Expected after invalidation groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List()) 631 } 632 } 633 634 // Tests function "GroupsAndMaybeResources" when the "aggregated" discovery is returned. 635 func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) { 636 tests := []struct { 637 name string 638 corev1 *apidiscovery.APIGroupDiscoveryList 639 apis *apidiscovery.APIGroupDiscoveryList 640 expectedGroupNames []string 641 expectedGroupVersions []string 642 expectedGVKs []string 643 expectedFailedGVs []string 644 }{ 645 { 646 name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/1 resources at /apis", 647 corev1: &apidiscovery.APIGroupDiscoveryList{ 648 Items: []apidiscovery.APIGroupDiscovery{ 649 { 650 Versions: []apidiscovery.APIVersionDiscovery{ 651 { 652 Version: "v1", 653 Resources: []apidiscovery.APIResourceDiscovery{ 654 { 655 Resource: "pods", 656 ResponseKind: &metav1.GroupVersionKind{ 657 Group: "", 658 Version: "v1", 659 Kind: "Pod", 660 }, 661 Scope: apidiscovery.ScopeNamespace, 662 }, 663 }, 664 }, 665 }, 666 }, 667 }, 668 }, 669 apis: &apidiscovery.APIGroupDiscoveryList{ 670 Items: []apidiscovery.APIGroupDiscovery{ 671 { 672 ObjectMeta: metav1.ObjectMeta{ 673 Name: "apps", 674 }, 675 Versions: []apidiscovery.APIVersionDiscovery{ 676 { 677 Version: "v1", 678 Resources: []apidiscovery.APIResourceDiscovery{ 679 { 680 Resource: "deployments", 681 ResponseKind: &metav1.GroupVersionKind{ 682 Group: "apps", 683 Version: "v1", 684 Kind: "Deployment", 685 }, 686 Scope: apidiscovery.ScopeNamespace, 687 }, 688 }, 689 }, 690 }, 691 }, 692 }, 693 }, 694 expectedGroupNames: []string{"", "apps"}, 695 expectedGroupVersions: []string{"v1", "apps/v1"}, 696 expectedGVKs: []string{ 697 "/v1/Pod", 698 "apps/v1/Deployment", 699 }, 700 expectedFailedGVs: []string{}, 701 }, 702 { 703 name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources/1 stale GV at /apis", 704 corev1: &apidiscovery.APIGroupDiscoveryList{ 705 Items: []apidiscovery.APIGroupDiscovery{ 706 { 707 Versions: []apidiscovery.APIVersionDiscovery{ 708 { 709 Version: "v1", 710 Resources: []apidiscovery.APIResourceDiscovery{ 711 { 712 Resource: "pods", 713 ResponseKind: &metav1.GroupVersionKind{ 714 Group: "", 715 Version: "v1", 716 Kind: "Pod", 717 }, 718 Scope: apidiscovery.ScopeNamespace, 719 }, 720 }, 721 }, 722 }, 723 }, 724 }, 725 }, 726 apis: &apidiscovery.APIGroupDiscoveryList{ 727 Items: []apidiscovery.APIGroupDiscovery{ 728 { 729 ObjectMeta: metav1.ObjectMeta{ 730 Name: "apps", 731 }, 732 Versions: []apidiscovery.APIVersionDiscovery{ 733 { 734 Version: "v1", 735 Resources: []apidiscovery.APIResourceDiscovery{ 736 { 737 Resource: "deployments", 738 ResponseKind: &metav1.GroupVersionKind{ 739 Group: "apps", 740 Version: "v1", 741 Kind: "Deployment", 742 }, 743 Scope: apidiscovery.ScopeNamespace, 744 }, 745 }, 746 }, 747 { 748 // Stale Version is not included in discovery. 749 Version: "v2", 750 Resources: []apidiscovery.APIResourceDiscovery{ 751 { 752 Resource: "deployments", 753 ResponseKind: &metav1.GroupVersionKind{ 754 Group: "apps", 755 Version: "v2", 756 Kind: "Deployment", 757 }, 758 Scope: apidiscovery.ScopeNamespace, 759 }, 760 }, 761 Freshness: apidiscovery.DiscoveryFreshnessStale, 762 }, 763 }, 764 }, 765 }, 766 }, 767 expectedGroupNames: []string{"", "apps"}, 768 expectedGroupVersions: []string{"v1", "apps/v1"}, 769 expectedGVKs: []string{ 770 "/v1/Pod", 771 "apps/v1/Deployment", 772 }, 773 expectedFailedGVs: []string{"apps/v2"}, 774 }, 775 { 776 name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis", 777 corev1: &apidiscovery.APIGroupDiscoveryList{ 778 Items: []apidiscovery.APIGroupDiscovery{ 779 { 780 Versions: []apidiscovery.APIVersionDiscovery{ 781 { 782 Version: "v1", 783 Resources: []apidiscovery.APIResourceDiscovery{ 784 { 785 Resource: "pods", 786 ResponseKind: &metav1.GroupVersionKind{ 787 Group: "", 788 Version: "v1", 789 Kind: "Pod", 790 }, 791 Scope: apidiscovery.ScopeNamespace, 792 }, 793 { 794 Resource: "services", 795 ResponseKind: &metav1.GroupVersionKind{ 796 Group: "", 797 Version: "v1", 798 Kind: "Service", 799 }, 800 Scope: apidiscovery.ScopeNamespace, 801 }, 802 }, 803 }, 804 }, 805 }, 806 }, 807 }, 808 apis: &apidiscovery.APIGroupDiscoveryList{ 809 Items: []apidiscovery.APIGroupDiscovery{ 810 { 811 ObjectMeta: metav1.ObjectMeta{ 812 Name: "apps", 813 }, 814 Versions: []apidiscovery.APIVersionDiscovery{ 815 { 816 Version: "v1", 817 Resources: []apidiscovery.APIResourceDiscovery{ 818 { 819 Resource: "deployments", 820 ResponseKind: &metav1.GroupVersionKind{ 821 Group: "apps", 822 Version: "v1", 823 Kind: "Deployment", 824 }, 825 Scope: apidiscovery.ScopeNamespace, 826 }, 827 { 828 Resource: "statefulsets", 829 ResponseKind: &metav1.GroupVersionKind{ 830 Group: "apps", 831 Version: "v1", 832 Kind: "StatefulSet", 833 }, 834 Scope: apidiscovery.ScopeNamespace, 835 }, 836 }, 837 }, 838 }, 839 }, 840 }, 841 }, 842 expectedGroupNames: []string{"", "apps"}, 843 expectedGroupVersions: []string{"v1", "apps/v1"}, 844 expectedGVKs: []string{ 845 "/v1/Pod", 846 "/v1/Service", 847 "apps/v1/Deployment", 848 "apps/v1/StatefulSet", 849 }, 850 expectedFailedGVs: []string{}, 851 }, 852 { 853 name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources/1 stale GV at /apis", 854 corev1: &apidiscovery.APIGroupDiscoveryList{ 855 Items: []apidiscovery.APIGroupDiscovery{ 856 { 857 Versions: []apidiscovery.APIVersionDiscovery{ 858 { 859 Version: "v1", 860 Resources: []apidiscovery.APIResourceDiscovery{ 861 { 862 Resource: "pods", 863 ResponseKind: &metav1.GroupVersionKind{ 864 Group: "", 865 Version: "v1", 866 Kind: "Pod", 867 }, 868 Scope: apidiscovery.ScopeNamespace, 869 }, 870 { 871 Resource: "services", 872 ResponseKind: &metav1.GroupVersionKind{ 873 Group: "", 874 Version: "v1", 875 Kind: "Service", 876 }, 877 Scope: apidiscovery.ScopeNamespace, 878 }, 879 }, 880 }, 881 }, 882 }, 883 }, 884 }, 885 apis: &apidiscovery.APIGroupDiscoveryList{ 886 Items: []apidiscovery.APIGroupDiscovery{ 887 { 888 ObjectMeta: metav1.ObjectMeta{ 889 Name: "apps", 890 }, 891 Versions: []apidiscovery.APIVersionDiscovery{ 892 { 893 Version: "v1", 894 Resources: []apidiscovery.APIResourceDiscovery{ 895 { 896 Resource: "deployments", 897 ResponseKind: &metav1.GroupVersionKind{ 898 Group: "apps", 899 Version: "v1", 900 Kind: "Deployment", 901 }, 902 Scope: apidiscovery.ScopeNamespace, 903 }, 904 { 905 Resource: "statefulsets", 906 ResponseKind: &metav1.GroupVersionKind{ 907 Group: "apps", 908 Version: "v1", 909 Kind: "StatefulSet", 910 }, 911 Scope: apidiscovery.ScopeNamespace, 912 }, 913 }, 914 }, 915 { 916 // Stale version is not included in discovery. 917 Version: "v1beta1", 918 Resources: []apidiscovery.APIResourceDiscovery{ 919 { 920 Resource: "deployments", 921 ResponseKind: &metav1.GroupVersionKind{ 922 Group: "apps", 923 Version: "v1beta1", 924 Kind: "Deployment", 925 }, 926 Scope: apidiscovery.ScopeNamespace, 927 }, 928 { 929 Resource: "statefulsets", 930 ResponseKind: &metav1.GroupVersionKind{ 931 Group: "apps", 932 Version: "v1beta1", 933 Kind: "StatefulSet", 934 }, 935 Scope: apidiscovery.ScopeNamespace, 936 }, 937 }, 938 Freshness: apidiscovery.DiscoveryFreshnessStale, 939 }, 940 }, 941 }, 942 { 943 ObjectMeta: metav1.ObjectMeta{ 944 Name: "batch", 945 }, 946 Versions: []apidiscovery.APIVersionDiscovery{ 947 { 948 Version: "v1", 949 Resources: []apidiscovery.APIResourceDiscovery{ 950 { 951 Resource: "jobs", 952 ResponseKind: &metav1.GroupVersionKind{ 953 Group: "batch", 954 Version: "v1", 955 Kind: "Job", 956 }, 957 Scope: apidiscovery.ScopeNamespace, 958 }, 959 { 960 Resource: "cronjobs", 961 ResponseKind: &metav1.GroupVersionKind{ 962 Group: "batch", 963 Version: "v1", 964 Kind: "CronJob", 965 }, 966 Scope: apidiscovery.ScopeNamespace, 967 }, 968 }, 969 }, 970 }, 971 }, 972 }, 973 }, 974 expectedGroupNames: []string{"", "apps", "batch"}, 975 expectedGroupVersions: []string{"v1", "apps/v1", "batch/v1"}, 976 expectedGVKs: []string{ 977 "/v1/Pod", 978 "/v1/Service", 979 "apps/v1/Deployment", 980 "apps/v1/StatefulSet", 981 "batch/v1/Job", 982 "batch/v1/CronJob", 983 }, 984 expectedFailedGVs: []string{"apps/v1beta1"}, 985 }, 986 { 987 name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources/2 stale GV at /apis", 988 corev1: &apidiscovery.APIGroupDiscoveryList{}, 989 apis: &apidiscovery.APIGroupDiscoveryList{ 990 Items: []apidiscovery.APIGroupDiscovery{ 991 { 992 ObjectMeta: metav1.ObjectMeta{ 993 Name: "apps", 994 }, 995 Versions: []apidiscovery.APIVersionDiscovery{ 996 // Statel "v1" Version is not included in discovery. 997 { 998 Version: "v1", 999 Resources: []apidiscovery.APIResourceDiscovery{ 1000 { 1001 Resource: "deployments", 1002 ResponseKind: &metav1.GroupVersionKind{ 1003 Group: "apps", 1004 Version: "v1", 1005 Kind: "Deployment", 1006 }, 1007 Scope: apidiscovery.ScopeNamespace, 1008 }, 1009 { 1010 Resource: "statefulsets", 1011 ResponseKind: &metav1.GroupVersionKind{ 1012 Group: "apps", 1013 Version: "v1", 1014 Kind: "StatefulSet", 1015 }, 1016 Scope: apidiscovery.ScopeNamespace, 1017 }, 1018 }, 1019 Freshness: apidiscovery.DiscoveryFreshnessStale, 1020 }, 1021 { 1022 Version: "v1beta1", 1023 Resources: []apidiscovery.APIResourceDiscovery{ 1024 { 1025 Resource: "deployments", 1026 ResponseKind: &metav1.GroupVersionKind{ 1027 Group: "apps", 1028 Version: "v1beta1", 1029 Kind: "Deployment", 1030 }, 1031 Scope: apidiscovery.ScopeNamespace, 1032 }, 1033 { 1034 Resource: "statefulsets", 1035 ResponseKind: &metav1.GroupVersionKind{ 1036 Group: "apps", 1037 Version: "v1beta1", 1038 Kind: "StatefulSet", 1039 }, 1040 Scope: apidiscovery.ScopeNamespace, 1041 }, 1042 }, 1043 }, 1044 }, 1045 }, 1046 { 1047 ObjectMeta: metav1.ObjectMeta{ 1048 Name: "batch", 1049 }, 1050 Versions: []apidiscovery.APIVersionDiscovery{ 1051 { 1052 Version: "v1", 1053 Resources: []apidiscovery.APIResourceDiscovery{ 1054 { 1055 Resource: "jobs", 1056 ResponseKind: &metav1.GroupVersionKind{ 1057 Group: "batch", 1058 Version: "v1", 1059 Kind: "Job", 1060 }, 1061 Scope: apidiscovery.ScopeNamespace, 1062 }, 1063 { 1064 Resource: "cronjobs", 1065 ResponseKind: &metav1.GroupVersionKind{ 1066 Group: "batch", 1067 Version: "v1", 1068 Kind: "CronJob", 1069 }, 1070 Scope: apidiscovery.ScopeNamespace, 1071 }, 1072 }, 1073 }, 1074 { 1075 // Stale Version is not included in discovery. 1076 Version: "v1beta1", 1077 Resources: []apidiscovery.APIResourceDiscovery{ 1078 { 1079 Resource: "jobs", 1080 ResponseKind: &metav1.GroupVersionKind{ 1081 Group: "batch", 1082 Version: "v1beta1", 1083 Kind: "Job", 1084 }, 1085 Scope: apidiscovery.ScopeNamespace, 1086 }, 1087 }, 1088 Freshness: apidiscovery.DiscoveryFreshnessStale, 1089 }, 1090 }, 1091 }, 1092 }, 1093 }, 1094 expectedGroupNames: []string{"apps", "batch"}, 1095 expectedGroupVersions: []string{"apps/v1beta1", "batch/v1"}, 1096 expectedGVKs: []string{ 1097 "apps/v1beta1/Deployment", 1098 "apps/v1beta1/StatefulSet", 1099 "batch/v1/Job", 1100 "batch/v1/CronJob", 1101 }, 1102 expectedFailedGVs: []string{"apps/v1", "batch/v1beta1"}, 1103 }, 1104 } 1105 1106 for _, test := range tests { 1107 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1108 var agg *apidiscovery.APIGroupDiscoveryList 1109 switch req.URL.Path { 1110 case "/api": 1111 agg = test.corev1 1112 case "/apis": 1113 agg = test.apis 1114 default: 1115 w.WriteHeader(http.StatusNotFound) 1116 return 1117 } 1118 output, err := json.Marshal(agg) 1119 require.NoError(t, err) 1120 // Content-type is "aggregated" discovery format. 1121 w.Header().Set("Content-Type", discovery.AcceptV2) 1122 w.WriteHeader(http.StatusOK) 1123 w.Write(output) 1124 })) 1125 defer server.Close() 1126 client := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: server.URL}) 1127 memClient := memCacheClient{ 1128 delegate: client, 1129 groupToServerResources: map[string]*cacheEntry{}, 1130 } 1131 assert.False(t, memClient.Fresh()) 1132 apiGroupList, resourcesMap, failedGVs, err := memClient.GroupsAndMaybeResources() 1133 require.NoError(t, err) 1134 assert.True(t, memClient.receivedAggregatedDiscovery) 1135 assert.True(t, memClient.Fresh()) 1136 // Test the expected groups are returned for the aggregated format. 1137 expectedGroupNames := sets.NewString(test.expectedGroupNames...) 1138 actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...) 1139 assert.True(t, expectedGroupNames.Equal(actualGroupNames), 1140 "%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List()) 1141 // Test the expected group versions for the aggregated discovery is correct. 1142 expectedGroupVersions := sets.NewString(test.expectedGroupVersions...) 1143 actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...) 1144 assert.True(t, expectedGroupVersions.Equal(actualGroupVersions), 1145 "%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List()) 1146 // Test the resources are correct. 1147 expectedGVKs := sets.NewString(test.expectedGVKs...) 1148 resources := []*metav1.APIResourceList{} 1149 for _, resourceList := range resourcesMap { 1150 resources = append(resources, resourceList) 1151 } 1152 actualGVKs := sets.NewString(groupVersionKinds(resources)...) 1153 assert.True(t, expectedGVKs.Equal(actualGVKs), 1154 "%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List()) 1155 // Test the returned failed GroupVersions are correct. 1156 expectedFailedGVs := sets.NewString(test.expectedFailedGVs...) 1157 actualFailedGVs := sets.NewString(failedGroupVersions(failedGVs)...) 1158 assert.True(t, expectedFailedGVs.Equal(actualFailedGVs), 1159 "%s: Expected Failed GroupVersions (%s), got (%s)", test.name, expectedFailedGVs.List(), actualFailedGVs.List()) 1160 // Invalidate the cache and retrieve the server groups again. 1161 memClient.Invalidate() 1162 assert.False(t, memClient.Fresh()) 1163 apiGroupList, _, _, err = memClient.GroupsAndMaybeResources() 1164 1165 require.NoError(t, err) 1166 // Test the expected groups are returned for the aggregated format. 1167 actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...) 1168 assert.True(t, expectedGroupNames.Equal(actualGroupNames), 1169 "%s: Expected after invalidation groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List()) 1170 } 1171 } 1172 1173 // Tests function "ServerGroups" when the "aggregated" discovery is returned. 1174 func TestMemCacheAggregatedServerGroups(t *testing.T) { 1175 tests := []struct { 1176 name string 1177 corev1 *apidiscovery.APIGroupDiscoveryList 1178 apis *apidiscovery.APIGroupDiscoveryList 1179 expectedGroupNames []string 1180 expectedGroupVersions []string 1181 expectedPreferredVersions []string 1182 }{ 1183 { 1184 name: "Aggregated discovery: 1 group/1 version at /api, 1 group/1 version at /apis", 1185 corev1: &apidiscovery.APIGroupDiscoveryList{ 1186 Items: []apidiscovery.APIGroupDiscovery{ 1187 { 1188 Versions: []apidiscovery.APIVersionDiscovery{ 1189 { 1190 Version: "v1", 1191 Resources: []apidiscovery.APIResourceDiscovery{ 1192 { 1193 Resource: "pods", 1194 ResponseKind: &metav1.GroupVersionKind{ 1195 Group: "", 1196 Version: "v1", 1197 Kind: "Pod", 1198 }, 1199 Scope: apidiscovery.ScopeNamespace, 1200 }, 1201 }, 1202 }, 1203 }, 1204 }, 1205 }, 1206 }, 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 }, 1228 }, 1229 }, 1230 }, 1231 }, 1232 expectedGroupNames: []string{"", "apps"}, 1233 expectedGroupVersions: []string{"v1", "apps/v1"}, 1234 expectedPreferredVersions: []string{"v1", "apps/v1"}, 1235 }, 1236 { 1237 name: "Aggregated discovery: 1 group/1 version at /api, 1 group/2 versions at /apis", 1238 corev1: &apidiscovery.APIGroupDiscoveryList{ 1239 Items: []apidiscovery.APIGroupDiscovery{ 1240 { 1241 ObjectMeta: metav1.ObjectMeta{ 1242 Name: "", 1243 }, 1244 Versions: []apidiscovery.APIVersionDiscovery{ 1245 { 1246 Version: "v1", 1247 Resources: []apidiscovery.APIResourceDiscovery{ 1248 { 1249 Resource: "pods", 1250 ResponseKind: &metav1.GroupVersionKind{ 1251 Group: "", 1252 Version: "v1", 1253 Kind: "Pod", 1254 }, 1255 Scope: apidiscovery.ScopeNamespace, 1256 }, 1257 }, 1258 }, 1259 }, 1260 }, 1261 }, 1262 }, 1263 apis: &apidiscovery.APIGroupDiscoveryList{ 1264 Items: []apidiscovery.APIGroupDiscovery{ 1265 { 1266 ObjectMeta: metav1.ObjectMeta{ 1267 Name: "apps", 1268 }, 1269 Versions: []apidiscovery.APIVersionDiscovery{ 1270 // v2 is preferred since it is first 1271 { 1272 Version: "v2", 1273 Resources: []apidiscovery.APIResourceDiscovery{ 1274 { 1275 Resource: "deployments", 1276 ResponseKind: &metav1.GroupVersionKind{ 1277 Group: "apps", 1278 Version: "v2", 1279 Kind: "Deployment", 1280 }, 1281 Scope: apidiscovery.ScopeNamespace, 1282 }, 1283 }, 1284 }, 1285 { 1286 Version: "v1", 1287 Resources: []apidiscovery.APIResourceDiscovery{ 1288 { 1289 Resource: "deployments", 1290 ResponseKind: &metav1.GroupVersionKind{ 1291 Group: "apps", 1292 Version: "v1", 1293 Kind: "Deployment", 1294 }, 1295 Scope: apidiscovery.ScopeNamespace, 1296 }, 1297 }, 1298 }, 1299 }, 1300 }, 1301 }, 1302 }, 1303 expectedGroupNames: []string{"", "apps"}, 1304 expectedGroupVersions: []string{"v1", "apps/v1", "apps/v2"}, 1305 expectedPreferredVersions: []string{"v1", "apps/v2"}, 1306 }, 1307 { 1308 name: "Aggregated discovery: /api returns nothing, 2 groups at /apis", 1309 corev1: &apidiscovery.APIGroupDiscoveryList{}, 1310 apis: &apidiscovery.APIGroupDiscoveryList{ 1311 Items: []apidiscovery.APIGroupDiscovery{ 1312 { 1313 ObjectMeta: metav1.ObjectMeta{ 1314 Name: "apps", 1315 }, 1316 Versions: []apidiscovery.APIVersionDiscovery{ 1317 { 1318 Version: "v1", 1319 Resources: []apidiscovery.APIResourceDiscovery{ 1320 { 1321 Resource: "deployments", 1322 ResponseKind: &metav1.GroupVersionKind{ 1323 Group: "apps", 1324 Version: "v1", 1325 Kind: "Deployment", 1326 }, 1327 Scope: apidiscovery.ScopeNamespace, 1328 }, 1329 { 1330 Resource: "statefulsets", 1331 ResponseKind: &metav1.GroupVersionKind{ 1332 Group: "apps", 1333 Version: "v1", 1334 Kind: "StatefulSet", 1335 }, 1336 Scope: apidiscovery.ScopeNamespace, 1337 }, 1338 }, 1339 }, 1340 }, 1341 }, 1342 { 1343 ObjectMeta: metav1.ObjectMeta{ 1344 Name: "batch", 1345 }, 1346 Versions: []apidiscovery.APIVersionDiscovery{ 1347 // v1 is preferred since it is first 1348 { 1349 Version: "v1", 1350 Resources: []apidiscovery.APIResourceDiscovery{ 1351 { 1352 Resource: "jobs", 1353 ResponseKind: &metav1.GroupVersionKind{ 1354 Group: "batch", 1355 Version: "v1", 1356 Kind: "Job", 1357 }, 1358 Scope: apidiscovery.ScopeNamespace, 1359 }, 1360 { 1361 Resource: "cronjobs", 1362 ResponseKind: &metav1.GroupVersionKind{ 1363 Group: "batch", 1364 Version: "v1", 1365 Kind: "CronJob", 1366 }, 1367 Scope: apidiscovery.ScopeNamespace, 1368 }, 1369 }, 1370 }, 1371 { 1372 Version: "v1beta1", 1373 Resources: []apidiscovery.APIResourceDiscovery{ 1374 { 1375 Resource: "jobs", 1376 ResponseKind: &metav1.GroupVersionKind{ 1377 Group: "batch", 1378 Version: "v1beta1", 1379 Kind: "Job", 1380 }, 1381 Scope: apidiscovery.ScopeNamespace, 1382 }, 1383 { 1384 Resource: "cronjobs", 1385 ResponseKind: &metav1.GroupVersionKind{ 1386 Group: "batch", 1387 Version: "v1beta1", 1388 Kind: "CronJob", 1389 }, 1390 Scope: apidiscovery.ScopeNamespace, 1391 }, 1392 }, 1393 }, 1394 }, 1395 }, 1396 }, 1397 }, 1398 expectedGroupNames: []string{"apps", "batch"}, 1399 expectedGroupVersions: []string{"apps/v1", "batch/v1", "batch/v1beta1"}, 1400 expectedPreferredVersions: []string{"apps/v1", "batch/v1"}, 1401 }, 1402 } 1403 1404 for _, test := range tests { 1405 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1406 var agg *apidiscovery.APIGroupDiscoveryList 1407 switch req.URL.Path { 1408 case "/api": 1409 agg = test.corev1 1410 case "/apis": 1411 agg = test.apis 1412 default: 1413 w.WriteHeader(http.StatusNotFound) 1414 return 1415 } 1416 output, err := json.Marshal(agg) 1417 require.NoError(t, err) 1418 // Content-type is "aggregated" discovery format. 1419 w.Header().Set("Content-Type", discovery.AcceptV2Beta1) 1420 w.WriteHeader(http.StatusOK) 1421 w.Write(output) 1422 })) 1423 defer server.Close() 1424 client := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: server.URL}) 1425 memCacheClient := NewMemCacheClient(client) 1426 assert.False(t, memCacheClient.Fresh()) 1427 apiGroupList, err := memCacheClient.ServerGroups() 1428 require.NoError(t, err) 1429 assert.True(t, memCacheClient.Fresh()) 1430 // Test the expected groups are returned for the aggregated format. 1431 expectedGroupNames := sets.NewString(test.expectedGroupNames...) 1432 actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...) 1433 assert.True(t, expectedGroupNames.Equal(actualGroupNames), 1434 "%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List()) 1435 // Test the expected group versions for the aggregated discovery is correct. 1436 expectedGroupVersions := sets.NewString(test.expectedGroupVersions...) 1437 actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...) 1438 assert.True(t, expectedGroupVersions.Equal(actualGroupVersions), 1439 "%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List()) 1440 // Test the groups preferred version is correct. 1441 expectedPreferredVersions := sets.NewString(test.expectedPreferredVersions...) 1442 actualPreferredVersions := sets.NewString(preferredVersionsFromList(apiGroupList)...) 1443 assert.True(t, expectedPreferredVersions.Equal(actualPreferredVersions), 1444 "%s: Expected preferred group/version (%s), got (%s)", test.name, expectedPreferredVersions.List(), actualPreferredVersions.List()) 1445 // Invalidate the cache and retrieve the server groups again. 1446 memCacheClient.Invalidate() 1447 assert.False(t, memCacheClient.Fresh()) 1448 apiGroupList, err = memCacheClient.ServerGroups() 1449 require.NoError(t, err) 1450 // Test the expected groups are returned for the aggregated format. 1451 actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...) 1452 assert.True(t, expectedGroupNames.Equal(actualGroupNames), 1453 "%s: Expected after invalidation groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List()) 1454 } 1455 } 1456 1457 func groupNamesFromList(groups *metav1.APIGroupList) []string { 1458 result := []string{} 1459 for _, group := range groups.Groups { 1460 result = append(result, group.Name) 1461 } 1462 return result 1463 } 1464 1465 func preferredVersionsFromList(groups *metav1.APIGroupList) []string { 1466 result := []string{} 1467 for _, group := range groups.Groups { 1468 preferredGV := group.PreferredVersion.GroupVersion 1469 result = append(result, preferredGV) 1470 } 1471 return result 1472 } 1473 1474 func groupVersionsFromGroups(groups *metav1.APIGroupList) []string { 1475 result := []string{} 1476 for _, group := range groups.Groups { 1477 for _, version := range group.Versions { 1478 result = append(result, version.GroupVersion) 1479 } 1480 } 1481 return result 1482 } 1483 1484 func groupVersionKinds(resources []*metav1.APIResourceList) []string { 1485 result := []string{} 1486 for _, resourceList := range resources { 1487 for _, resource := range resourceList.APIResources { 1488 gvk := fmt.Sprintf("%s/%s/%s", resource.Group, resource.Version, resource.Kind) 1489 result = append(result, gvk) 1490 } 1491 } 1492 return result 1493 } 1494 1495 func failedGroupVersions(gvs map[schema.GroupVersion]error) []string { 1496 result := []string{} 1497 for gv := range gvs { 1498 result = append(result, gv.String()) 1499 } 1500 return result 1501 }