From 5859da3e1fd8ad3ac927fab8a412e06255f40ace Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Thu, 15 Oct 2015 16:34:30 -0700 Subject: [PATCH] add discovery client --- .../app/controllermanager.go | 2 +- pkg/client/unversioned/client.go | 49 +---- pkg/client/unversioned/client_test.go | 4 +- pkg/client/unversioned/discovery_client.go | 175 ++++++++++++++++++ pkg/client/unversioned/helper.go | 11 +- .../unversioned/testclient/testclient.go | 42 +++-- .../namespace/namespace_controller.go | 2 +- .../namespace/namespace_controller_test.go | 4 +- 8 files changed, 226 insertions(+), 63 deletions(-) create mode 100644 pkg/client/unversioned/discovery_client.go diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 76eee70457d74..5a41fe7d36083 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -292,7 +292,7 @@ func (s *CMServer) Run(_ []string) error { } versions := &unversioned.APIVersions{Versions: versionStrings} - resourceMap, err := client.SupportedResources(kubeClient, kubeconfig) + resourceMap, err := kubeClient.Discovery().ServerResources() if err != nil { glog.Fatalf("Failed to get supported resources from server: %v", err) } diff --git a/pkg/client/unversioned/client.go b/pkg/client/unversioned/client.go index d9fe4646062ba..0381a90b15a59 100644 --- a/pkg/client/unversioned/client.go +++ b/pkg/client/unversioned/client.go @@ -52,7 +52,7 @@ type Interface interface { ComponentStatusesInterface SwaggerSchemaInterface Extensions() ExtensionsInterface - ResourcesInterface + Discovery() DiscoveryInterface } func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface { @@ -120,11 +120,6 @@ type VersionInterface interface { ServerAPIVersions() (*unversioned.APIVersions, error) } -// ResourcesInterface has methods for obtaining supported resources on the API server -type ResourcesInterface interface { - SupportedResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) -} - // APIStatus is exposed by errors that can be converted to an api.Status object // for finer grained details. type APIStatus interface { @@ -135,6 +130,8 @@ type APIStatus interface { type Client struct { *RESTClient *ExtensionsClient + // TODO: remove this when we re-structure pkg/client. + *DiscoveryClient } // ServerVersion retrieves and parses the server's version. @@ -151,42 +148,6 @@ func (c *Client) ServerVersion() (*version.Info, error) { return &info, nil } -// SupportedResourcesForGroupVersion retrieves the list of resources supported by the API server for a group version. -func (c *Client) SupportedResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) { - var prefix string - if groupVersion == "v1" { - prefix = "/api" - } else { - prefix = "/apis" - } - body, err := c.Get().AbsPath(prefix, groupVersion).Do().Raw() - if err != nil { - return nil, err - } - resources := unversioned.APIResourceList{} - if err := json.Unmarshal(body, &resources); err != nil { - return nil, err - } - return &resources, nil -} - -// SupportedResources gets all supported resources for all group versions. The key in the map is an API groupVersion. -func SupportedResources(c Interface, cfg *Config) (map[string]*unversioned.APIResourceList, error) { - apis, err := ServerAPIVersions(cfg) - if err != nil { - return nil, err - } - result := map[string]*unversioned.APIResourceList{} - for _, groupVersion := range apis { - resources, err := c.SupportedResourcesForGroupVersion(groupVersion) - if err != nil { - return nil, err - } - result[groupVersion] = resources - } - return result, nil -} - // ServerAPIVersions retrieves and parses the list of API versions the server supports. func (c *Client) ServerAPIVersions() (*unversioned.APIVersions, error) { body, err := c.Get().UnversionedPath("").Do().Raw() @@ -288,3 +249,7 @@ func IsTimeout(err error) bool { func (c *Client) Extensions() ExtensionsInterface { return c.ExtensionsClient } + +func (c *Client) Discovery() DiscoveryInterface { + return c.DiscoveryClient +} diff --git a/pkg/client/unversioned/client_test.go b/pkg/client/unversioned/client_test.go index ac31653b76902..be795e315f0fa 100644 --- a/pkg/client/unversioned/client_test.go +++ b/pkg/client/unversioned/client_test.go @@ -352,7 +352,7 @@ func TestGetServerResources(t *testing.T) { })) client := NewOrDie(&Config{Host: server.URL}) for _, test := range tests { - got, err := client.SupportedResourcesForGroupVersion(test.request) + got, err := client.Discovery().ServerResourcesForGroupVersion(test.request) if test.expectErr { if err == nil { t.Error("unexpected non-error") @@ -368,7 +368,7 @@ func TestGetServerResources(t *testing.T) { } } - resourceMap, err := SupportedResources(client, &Config{Host: server.URL}) + resourceMap, err := client.Discovery().ServerResources() if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/client/unversioned/discovery_client.go b/pkg/client/unversioned/discovery_client.go new file mode 100644 index 0000000000000..3b668cd9c37be --- /dev/null +++ b/pkg/client/unversioned/discovery_client.go @@ -0,0 +1,175 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package unversioned + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// DiscoveryInterface holds the methods that discover server-supported API groups, +// versions and resources. +type DiscoveryInterface interface { + ServerGroupsInterface + ServerResourcesInterface +} + +// GroupsInterface has methods for obtaining supported groups on the API server +type ServerGroupsInterface interface { + // ServerGroups returns the supported groups, with information like supported versions and the + // preferred version. + ServerGroups() (*unversioned.APIGroupList, error) +} + +// ServerResourcesInterface has methods for obtaining supported resources on the API server +type ServerResourcesInterface interface { + // ServerResourcesForGroupVersion returns the supported resources for a group and version. + ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) + // ServerResources returns the supported resources for all groups and versions. + ServerResources() (map[string]*unversioned.APIResourceList, error) +} + +// DiscoveryClient implements the functions that dicovery server-supported API groups, +// versions and resources. +type DiscoveryClient struct { + httpClient HTTPClient + baseURL url.URL +} + +// Convert unversioned.APIVersions to unversioned.APIGroup. APIVersions is used by legacy v1, so +// group would be "". +func apiVersionsToAPIGroup(apiVersions *unversioned.APIVersions) (apiGroup unversioned.APIGroup) { + groupVersions := []unversioned.GroupVersion{} + for _, version := range apiVersions.Versions { + groupVersion := unversioned.GroupVersion{ + GroupVersion: version, + Version: version, + } + groupVersions = append(groupVersions, groupVersion) + } + apiGroup.Versions = groupVersions + // There should be only one groupVersion returned at /api + apiGroup.PreferredVersion = groupVersions[0] + return +} + +func (d *DiscoveryClient) get(url string) (resp *http.Response, err error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return d.httpClient.Do(req) +} + +// APIGroups returns the supported groups, with information like supported versions and the +// preferred version. +func (d *DiscoveryClient) ServerGroups() (apiGroupList *unversioned.APIGroupList, err error) { + // Get the groupVersions exposed at /api + url := d.baseURL + url.Path = "/api" + resp, err := d.get(url.String()) + if err != nil { + return nil, err + } + var v unversioned.APIVersions + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&v) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + apiGroup := apiVersionsToAPIGroup(&v) + + // Get the groupVersions exposed at /apis + url.Path = "/apis" + resp2, err := d.get(url.String()) + if err != nil { + return nil, err + } + defer resp2.Body.Close() + apiGroupList = &unversioned.APIGroupList{} + if err = json.NewDecoder(resp2.Body).Decode(&apiGroupList); err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + + // append the group retrieved from /api to the list + apiGroupList.Groups = append(apiGroupList.Groups, apiGroup) + return apiGroupList, nil +} + +// APIResourcesForGroup returns the supported resources for a group and version. +func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (resources *unversioned.APIResourceList, err error) { + url := d.baseURL + if groupVersion == "v1" { + url.Path = "/api/" + groupVersion + } else { + url.Path = "/apis/" + groupVersion + } + resp, err := d.get(url.String()) + if err != nil { + return nil, err + } + defer resp.Body.Close() + resources = &unversioned.APIResourceList{} + if err = json.NewDecoder(resp.Body).Decode(resources); err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + return resources, nil +} + +// APIResources returns the supported resources for all groups and versions. +func (d *DiscoveryClient) ServerResources() (map[string]*unversioned.APIResourceList, error) { + apiGroups, err := d.ServerGroups() + if err != nil { + return nil, err + } + groupVersions := extractGroupVersions(apiGroups) + result := map[string]*unversioned.APIResourceList{} + for _, groupVersion := range groupVersions { + resources, err := d.ServerResourcesForGroupVersion(groupVersion) + if err != nil { + return nil, err + } + result[groupVersion] = resources + } + return result, nil +} + +func setDiscoveryDefaults(config *Config) error { + config.Prefix = "" + config.Version = "" + return nil +} + +// NewDiscoveryClient creates a new DiscoveryClient for the given config. This client +// can be used to discover supported resources in the API server. +func NewDiscoveryClient(c *Config) (*DiscoveryClient, error) { + config := *c + if err := setDiscoveryDefaults(&config); err != nil { + return nil, err + } + transport, err := TransportFor(c) + if err != nil { + return nil, err + } + client := &http.Client{Transport: transport} + baseURL, err := defaultServerUrlFor(c) + return &DiscoveryClient{client, *baseURL}, nil +} diff --git a/pkg/client/unversioned/helper.go b/pkg/client/unversioned/helper.go index 3e938ea4cdec3..b217ae07fd4b6 100644 --- a/pkg/client/unversioned/helper.go +++ b/pkg/client/unversioned/helper.go @@ -146,15 +146,22 @@ func New(c *Config) (*Client, error) { return nil, err } + discoveryConfig := *c + discoveryClient, err := NewDiscoveryClient(&discoveryConfig) + if err != nil { + return nil, err + } + if _, err := latest.Group("extensions"); err != nil { - return &Client{RESTClient: client, ExtensionsClient: nil}, nil + return &Client{RESTClient: client, ExtensionsClient: nil, DiscoveryClient: discoveryClient}, nil } experimentalConfig := *c experimentalClient, err := NewExtensions(&experimentalConfig) if err != nil { return nil, err } - return &Client{RESTClient: client, ExtensionsClient: experimentalClient}, nil + + return &Client{RESTClient: client, ExtensionsClient: experimentalClient, DiscoveryClient: discoveryClient}, nil } // MatchesServerVersion queries the server to compares the build version diff --git a/pkg/client/unversioned/testclient/testclient.go b/pkg/client/unversioned/testclient/testclient.go index fd35a45e629a8..296dca5c19633 100644 --- a/pkg/client/unversioned/testclient/testclient.go +++ b/pkg/client/unversioned/testclient/testclient.go @@ -61,7 +61,7 @@ type Fake struct { // ProxyReactionChain is the list of proxy reactors that will be attempted for every request in the order they are tried ProxyReactionChain []ProxyReactor - Resources []unversioned.APIResourceList + Resources map[string]*unversioned.APIResourceList } // Reactor is an interface to allow the composition of reaction functions. @@ -274,18 +274,8 @@ func (c *Fake) Extensions() client.ExtensionsInterface { return &FakeExperimental{c} } -func (c *Fake) SupportedResourcesForGroupVersion(version string) (*unversioned.APIResourceList, error) { - action := ActionImpl{ - Verb: "get", - Resource: "resource", - } - c.Invokes(action, nil) - for _, resource := range c.Resources { - if resource.GroupVersion == version { - return &resource, nil - } - } - return nil, nil +func (c *Fake) Discovery() client.DiscoveryInterface { + return &FakeDiscovery{c} } func (c *Fake) ServerVersion() (*version.Info, error) { @@ -348,3 +338,29 @@ func (c *FakeExperimental) Jobs(namespace string) client.JobInterface { func (c *FakeExperimental) Ingress(namespace string) client.IngressInterface { return &FakeIngress{Fake: c, Namespace: namespace} } + +type FakeDiscovery struct { + *Fake +} + +func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) { + action := ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + return c.Resources[groupVersion], nil +} + +func (c *FakeDiscovery) ServerResources() (map[string]*unversioned.APIResourceList, error) { + action := ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + return c.Resources, nil +} + +func (c *FakeDiscovery) ServerGroups() (*unversioned.APIGroupList, error) { + return nil, nil +} diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 66185eea76782..97f3484cb8c8c 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -194,7 +194,7 @@ func deleteAllContent(kubeClient client.Interface, versions *unversioned.APIVers } // If experimental mode, delete all experimental resources for the namespace. if containsVersion(versions, "extensions/v1beta1") { - resources, err := kubeClient.SupportedResourcesForGroupVersion("extensions/v1beta1") + resources, err := kubeClient.Discovery().ServerResourcesForGroupVersion("extensions/v1beta1") if err != nil { return estimate, err } diff --git a/pkg/controller/namespace/namespace_controller_test.go b/pkg/controller/namespace/namespace_controller_test.go index 56e5b58018bf4..d5c898bec6a7c 100644 --- a/pkg/controller/namespace/namespace_controller_test.go +++ b/pkg/controller/namespace/namespace_controller_test.go @@ -95,8 +95,8 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *unversioned.APIV for _, resource := range []string{"daemonsets", "deployments", "jobs", "horizontalpodautoscalers", "ingress"} { resources = append(resources, unversioned.APIResource{Name: resource}) } - mockClient.Resources = []unversioned.APIResourceList{ - { + mockClient.Resources = map[string]*unversioned.APIResourceList{ + "extensions/v1beta1": { GroupVersion: "extensions/v1beta1", APIResources: resources, },