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