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