diff --git a/pkg/kubectl/cmd/util/cached_discovery.go b/pkg/kubectl/cmd/util/cached_discovery.go index d053102bb61..60b3ed9bb48 100644 --- a/pkg/kubectl/cmd/util/cached_discovery.go +++ b/pkg/kubectl/cmd/util/cached_discovery.go @@ -91,20 +91,7 @@ func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion stri // ServerResources returns the supported resources for all groups and versions. func (d *CachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) { - apiGroups, err := d.ServerGroups() - if err != nil { - return nil, err - } - groupVersions := metav1.ExtractGroupVersions(apiGroups) - result := []*metav1.APIResourceList{} - for _, groupVersion := range groupVersions { - resources, err := d.ServerResourcesForGroupVersion(groupVersion) - if err != nil { - return nil, err - } - result = append(result, resources) - } - return result, nil + return discovery.ServerResources(d) } func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) { diff --git a/staging/src/k8s.io/client-go/discovery/BUILD b/staging/src/k8s.io/client-go/discovery/BUILD index 9274d11b552..397a390a57c 100644 --- a/staging/src/k8s.io/client-go/discovery/BUILD +++ b/staging/src/k8s.io/client-go/discovery/BUILD @@ -22,6 +22,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", @@ -71,6 +72,7 @@ go_test( "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", diff --git a/staging/src/k8s.io/client-go/discovery/discovery_client.go b/staging/src/k8s.io/client-go/discovery/discovery_client.go index d59e8a02ac1..cef4d40152a 100644 --- a/staging/src/k8s.io/client-go/discovery/discovery_client.go +++ b/staging/src/k8s.io/client-go/discovery/discovery_client.go @@ -22,6 +22,7 @@ import ( "net/url" "sort" "strings" + "sync" "time" "github.com/golang/protobuf/proto" @@ -32,6 +33,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/kubernetes/scheme" restclient "k8s.io/client-go/rest" @@ -191,33 +193,7 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r // serverResources returns the supported resources for all groups and versions. func (d *DiscoveryClient) serverResources() ([]*metav1.APIResourceList, error) { - apiGroups, err := d.ServerGroups() - if err != nil { - return nil, err - } - - result := []*metav1.APIResourceList{} - failedGroups := make(map[schema.GroupVersion]error) - - for _, apiGroup := range apiGroups.Groups { - for _, version := range apiGroup.Versions { - gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version} - resources, err := d.ServerResourcesForGroupVersion(version.GroupVersion) - if err != nil { - // TODO: maybe restrict this to NotFound errors - failedGroups[gv] = err - continue - } - - result = append(result, resources) - } - } - - if len(failedGroups) == 0 { - return result, nil - } - - return result, &ErrGroupDiscoveryFailed{Groups: failedGroups} + return ServerResources(d) } // ServerResources returns the supported resources for all groups and versions. @@ -253,6 +229,33 @@ func (d *DiscoveryClient) serverPreferredResources() ([]*metav1.APIResourceList, return ServerPreferredResources(d) } +// ServerResources uses the provided discovery interface to look up supported resources for all groups and versions. +func ServerResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) { + apiGroups, err := d.ServerGroups() + if err != nil { + return nil, err + } + + groupVersionResources, failedGroups := fetchGroupVersionResources(d, apiGroups) + + // order results by group/version discovery order + result := []*metav1.APIResourceList{} + for _, apiGroup := range apiGroups.Groups { + for _, version := range apiGroup.Versions { + gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version} + if resources, ok := groupVersionResources[gv]; ok { + result = append(result, resources) + } + } + } + + if len(failedGroups) == 0 { + return result, nil + } + + return result, &ErrGroupDiscoveryFailed{Groups: failedGroups} +} + // ServerPreferredResources uses the provided discovery interface to look up preferred resources func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) { serverGroupList, err := d.ServerGroups() @@ -260,9 +263,9 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, return nil, err } - result := []*metav1.APIResourceList{} - failedGroups := make(map[schema.GroupVersion]error) + groupVersionResources, failedGroups := fetchGroupVersionResources(d, serverGroupList) + result := []*metav1.APIResourceList{} grVersions := map[schema.GroupResource]string{} // selected version of a GroupResource grApiResources := map[schema.GroupResource]*metav1.APIResource{} // selected APIResource for a GroupResource gvApiResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // blueprint for a APIResourceList for later grouping @@ -270,10 +273,9 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, for _, apiGroup := range serverGroupList.Groups { for _, version := range apiGroup.Versions { groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version} - apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion) - if err != nil { - // TODO: maybe restrict this to NotFound errors - failedGroups[groupVersion] = err + + apiResourceList, ok := groupVersionResources[groupVersion] + if !ok { continue } @@ -315,6 +317,41 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, return result, &ErrGroupDiscoveryFailed{Groups: failedGroups} } +// fetchServerResourcesForGroupVersions uses the discovery client to fetch the resources for the specified groups in parallel +func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) { + groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList) + failedGroups := make(map[schema.GroupVersion]error) + + wg := &sync.WaitGroup{} + resultLock := &sync.Mutex{} + for _, apiGroup := range apiGroups.Groups { + for _, version := range apiGroup.Versions { + groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version} + wg.Add(1) + go func() { + defer wg.Done() + defer utilruntime.HandleCrash() + + apiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String()) + + // lock to record results + resultLock.Lock() + defer resultLock.Unlock() + + if err != nil { + // TODO: maybe restrict this to NotFound errors + failedGroups[groupVersion] = err + } else { + groupVersionResources[groupVersion] = apiResourceList + } + }() + } + } + wg.Wait() + + return groupVersionResources, failedGroups +} + // ServerPreferredResources returns the supported resources with the version preferred by the // server. func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) { diff --git a/staging/src/k8s.io/client-go/discovery/discovery_client_test.go b/staging/src/k8s.io/client-go/discovery/discovery_client_test.go index 1af25be747a..15e0a9e891d 100644 --- a/staging/src/k8s.io/client-go/discovery/discovery_client_test.go +++ b/staging/src/k8s.io/client-go/discovery/discovery_client_test.go @@ -32,6 +32,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/version" restclient "k8s.io/client-go/rest" @@ -204,6 +205,14 @@ func TestGetServerResources(t *testing.T) { {Name: "jobs", Namespaced: true, Kind: "Job"}, }, } + beta2 := metav1.APIResourceList{ + GroupVersion: "extensions/v1beta2", + APIResources: []metav1.APIResource{ + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "ingresses", Namespaced: true, Kind: "Ingress"}, + {Name: "jobs", Namespaced: true, Kind: "Job"}, + }, + } tests := []struct { resourcesList *metav1.APIResourceList path string @@ -236,6 +245,8 @@ func TestGetServerResources(t *testing.T) { list = &stable case "/apis/extensions/v1beta1": list = &beta + case "/apis/extensions/v1beta2": + list = &beta2 case "/api": list = &metav1.APIVersions{ Versions: []string{ @@ -246,8 +257,10 @@ func TestGetServerResources(t *testing.T) { list = &metav1.APIGroupList{ Groups: []metav1.APIGroup{ { + Name: "extensions", Versions: []metav1.GroupVersionForDiscovery{ - {GroupVersion: "extensions/v1beta1"}, + {GroupVersion: "extensions/v1beta1", Version: "v1beta1"}, + {GroupVersion: "extensions/v1beta2", Version: "v1beta2"}, }, }, }, @@ -289,11 +302,10 @@ func TestGetServerResources(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - serverGroupVersions := sets.NewString(groupVersions(serverResources)...) - for _, api := range []string{"v1", "extensions/v1beta1"} { - if !serverGroupVersions.Has(api) { - t.Errorf("missing expected api %q in %v", api, serverResources) - } + serverGroupVersions := groupVersions(serverResources) + expectedGroupVersions := []string{"v1", "extensions/v1beta1", "extensions/v1beta2"} + if !reflect.DeepEqual(expectedGroupVersions, serverGroupVersions) { + t.Errorf("unexpected group versions: %v", diff.ObjectReflectDiff(expectedGroupVersions, serverGroupVersions)) } } @@ -639,7 +651,7 @@ func TestServerPreferredResourcesRetries(t *testing.T) { { Name: "extensions", Versions: []metav1.GroupVersionForDiscovery{ - {GroupVersion: "extensions/v1beta1"}, + {GroupVersion: "extensions/v1beta1", Version: "v1beta1"}, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "extensions/v1beta1",