From 5ff923c7f931a34632df1f5a5400adbec33d49bf Mon Sep 17 00:00:00 2001 From: David Eads Date: Fri, 27 Apr 2018 11:29:05 -0400 Subject: [PATCH 1/2] make dynamic client slightly easier to use --- .../deletion/namespaced_resources_deleter.go | 6 +++--- .../test/integration/registration_test.go | 4 ++-- .../test/integration/testserver/resources.go | 4 ++-- .../src/k8s.io/client-go/dynamic/simple.go | 19 +++++++++++++------ test/e2e/apimachinery/aggregator.go | 2 +- test/e2e/apimachinery/crd_watch.go | 4 ++-- test/e2e/apimachinery/garbage_collector.go | 2 +- test/e2e/framework/crd_util.go | 2 +- test/e2e/framework/util.go | 4 ++-- .../garbage_collector_test.go | 2 +- 10 files changed, 28 insertions(+), 21 deletions(-) diff --git a/pkg/controller/namespace/deletion/namespaced_resources_deleter.go b/pkg/controller/namespace/deletion/namespaced_resources_deleter.go index 4929cfb4530..f92cf81f0d6 100644 --- a/pkg/controller/namespace/deletion/namespaced_resources_deleter.go +++ b/pkg/controller/namespace/deletion/namespaced_resources_deleter.go @@ -342,7 +342,7 @@ func (d *namespacedResourcesDeleter) deleteCollection(gvr schema.GroupVersionRes // namespace itself. background := metav1.DeletePropagationBackground opts := &metav1.DeleteOptions{PropagationPolicy: &background} - err := d.dynamicClient.NamespacedResource(gvr, namespace).DeleteCollection(opts, metav1.ListOptions{}) + err := d.dynamicClient.Resource(gvr).Namespace(namespace).DeleteCollection(opts, metav1.ListOptions{}) if err == nil { return true, nil @@ -378,7 +378,7 @@ func (d *namespacedResourcesDeleter) listCollection(gvr schema.GroupVersionResou return nil, false, nil } - unstructuredList, err := d.dynamicClient.NamespacedResource(gvr, namespace).List(metav1.ListOptions{IncludeUninitialized: true}) + unstructuredList, err := d.dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{IncludeUninitialized: true}) if err == nil { return unstructuredList, true, nil } @@ -412,7 +412,7 @@ func (d *namespacedResourcesDeleter) deleteEachItem(gvr schema.GroupVersionResou for _, item := range unstructuredList.Items { background := metav1.DeletePropagationBackground opts := &metav1.DeleteOptions{PropagationPolicy: &background} - if err = d.dynamicClient.NamespacedResource(gvr, namespace).Delete(item.GetName(), opts); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) { + if err = d.dynamicClient.Resource(gvr).Namespace(namespace).Delete(item.GetName(), opts); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) { return err } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go index 2594a9933af..9b5aa7bf390 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go @@ -72,9 +72,9 @@ func NewNamespacedCustomResourceClient(ns string, client dynamic.DynamicInterfac gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural} if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped { - return client.NamespacedResource(gvr, ns) + return client.Resource(gvr).Namespace(ns) } - return client.ClusterResource(gvr) + return client.Resource(gvr) } func NewNamespacedCustomResourceStatusClient(ns string, client dynamic.DynamicInterface, crd *apiextensionsv1beta1.CustomResourceDefinition) dynamic.DynamicResourceInterface { diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go index 6a7c655daa8..bb7060e38f8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go @@ -217,9 +217,9 @@ func checkForWatchCachePrimed(crd *apiextensionsv1beta1.CustomResourceDefinition gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural} var resourceClient dynamic.DynamicResourceInterface if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped { - resourceClient = dynamicClientSet.NamespacedResource(gvr, ns) + resourceClient = dynamicClientSet.Resource(gvr).Namespace(ns) } else { - resourceClient = dynamicClientSet.ClusterResource(gvr) + resourceClient = dynamicClientSet.Resource(gvr) } initialList, err := resourceClient.List(metav1.ListOptions{}) diff --git a/staging/src/k8s.io/client-go/dynamic/simple.go b/staging/src/k8s.io/client-go/dynamic/simple.go index 41e0360e0bf..396ff8766de 100644 --- a/staging/src/k8s.io/client-go/dynamic/simple.go +++ b/staging/src/k8s.io/client-go/dynamic/simple.go @@ -32,8 +32,7 @@ import ( ) type DynamicInterface interface { - ClusterResource(resource schema.GroupVersionResource) DynamicResourceInterface - NamespacedResource(resource schema.GroupVersionResource, namespace string) DynamicResourceInterface + Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface // Deprecated, this isn't how we want to do it ClusterSubresource(resource schema.GroupVersionResource, subresource string) DynamicResourceInterface @@ -53,6 +52,11 @@ type DynamicResourceInterface interface { Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) } +type NamespaceableDynamicResourceInterface interface { + Namespace(string) DynamicResourceInterface + DynamicResourceInterface +} + type dynamicClient struct { client *rest.RESTClient } @@ -86,12 +90,9 @@ type dynamicResourceClient struct { subresource string } -func (c *dynamicClient) ClusterResource(resource schema.GroupVersionResource) DynamicResourceInterface { +func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface { return &dynamicResourceClient{client: c, resource: resource} } -func (c *dynamicClient) NamespacedResource(resource schema.GroupVersionResource, namespace string) DynamicResourceInterface { - return &dynamicResourceClient{client: c, resource: resource, namespace: namespace} -} func (c *dynamicClient) ClusterSubresource(resource schema.GroupVersionResource, subresource string) DynamicResourceInterface { return &dynamicResourceClient{client: c, resource: resource, subresource: subresource} @@ -100,6 +101,12 @@ func (c *dynamicClient) NamespacedSubresource(resource schema.GroupVersionResour return &dynamicResourceClient{client: c, resource: resource, namespace: namespace, subresource: subresource} } +func (c *dynamicResourceClient) Namespace(ns string) DynamicResourceInterface { + ret := *c + ret.namespace = ns + return &ret +} + func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { if len(c.subresource) > 0 { return nil, fmt.Errorf("create not supported for subresources") diff --git a/test/e2e/apimachinery/aggregator.go b/test/e2e/apimachinery/aggregator.go index 54d8f306192..0aa64361680 100644 --- a/test/e2e/apimachinery/aggregator.go +++ b/test/e2e/apimachinery/aggregator.go @@ -391,7 +391,7 @@ func TestSampleAPIServer(f *framework.Framework, image string) { if !ok { framework.Failf("could not find group version resource for dynamic client and wardle/flunders.") } - dynamicClient := f.DynamicClient.NamespacedResource(gvr, namespace) + dynamicClient := f.DynamicClient.Resource(gvr).Namespace(namespace) // kubectl create -f flunders-1.yaml // Request Body: {"apiVersion":"wardle.k8s.io/v1alpha1","kind":"Flunder","metadata":{"labels":{"sample-label":"true"},"name":"test-flunder","namespace":"default"}} diff --git a/test/e2e/apimachinery/crd_watch.go b/test/e2e/apimachinery/crd_watch.go index ff2c830844f..6c38a50ae85 100644 --- a/test/e2e/apimachinery/crd_watch.go +++ b/test/e2e/apimachinery/crd_watch.go @@ -158,9 +158,9 @@ func newNamespacedCustomResourceClient(ns string, client dynamic.DynamicInterfac gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural} if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped { - return client.NamespacedResource(gvr, ns) + return client.Resource(gvr).Namespace(ns) } else { - return client.ClusterResource(gvr) + return client.Resource(gvr) } } diff --git a/test/e2e/apimachinery/garbage_collector.go b/test/e2e/apimachinery/garbage_collector.go index 1f0e43ef58b..d8a37575618 100644 --- a/test/e2e/apimachinery/garbage_collector.go +++ b/test/e2e/apimachinery/garbage_collector.go @@ -924,7 +924,7 @@ var _ = SIGDescribe("Garbage collector", func() { // Get a client for the custom resource. gvr := schema.GroupVersionResource{Group: definition.Spec.Group, Version: definition.Spec.Version, Resource: definition.Spec.Names.Plural} - resourceClient := f.DynamicClient.ClusterResource(gvr) + resourceClient := f.DynamicClient.Resource(gvr) apiVersion := definition.Spec.Group + "/" + definition.Spec.Version diff --git a/test/e2e/framework/crd_util.go b/test/e2e/framework/crd_util.go index 08b90100af9..58cbdcc976f 100644 --- a/test/e2e/framework/crd_util.go +++ b/test/e2e/framework/crd_util.go @@ -83,7 +83,7 @@ func CreateTestCRD(f *Framework) (*TestCrd, error) { } gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural} - resourceClient := dynamicClient.NamespacedResource(gvr, f.Namespace.Name) + resourceClient := dynamicClient.Resource(gvr).Namespace(f.Namespace.Name) testcrd.ApiExtensionClient = apiExtensionClient testcrd.Crd = crd diff --git a/test/e2e/framework/util.go b/test/e2e/framework/util.go index 62a221a249e..c8a98494fb0 100644 --- a/test/e2e/framework/util.go +++ b/test/e2e/framework/util.go @@ -508,7 +508,7 @@ func SkipUnlessServerVersionGTE(v *utilversion.Version, c discovery.ServerVersio } func SkipIfMissingResource(dynamicClient dynamic.DynamicInterface, gvr schema.GroupVersionResource, namespace string) { - resourceClient := dynamicClient.NamespacedResource(gvr, namespace) + resourceClient := dynamicClient.Resource(gvr).Namespace(namespace) _, err := resourceClient.List(metav1.ListOptions{}) if err != nil { // not all resources support list, so we ignore those @@ -1258,7 +1258,7 @@ func hasRemainingContent(c clientset.Interface, dynamicClient dynamic.DynamicInt // dump how many of resource type is on the server in a log. for gvr := range groupVersionResources { // get a client for this group version... - dynamicClient := dynamicClient.NamespacedResource(gvr, namespace) + dynamicClient := dynamicClient.Resource(gvr).Namespace(namespace) if err != nil { // not all resource types support list, so some errors here are normal depending on the resource type. Logf("namespace: %s, unable to get client - gvr: %v, error: %v", namespace, gvr, err) diff --git a/test/integration/garbagecollector/garbage_collector_test.go b/test/integration/garbagecollector/garbage_collector_test.go index 142de1a2d5d..8e19f2404ee 100644 --- a/test/integration/garbagecollector/garbage_collector_test.go +++ b/test/integration/garbagecollector/garbage_collector_test.go @@ -183,7 +183,7 @@ func createRandomCustomResourceDefinition( // Get a client for the custom resource. gvr := schema.GroupVersionResource{Group: definition.Spec.Group, Version: definition.Spec.Version, Resource: definition.Spec.Names.Plural} - resourceClient := dynamicClient.NamespacedResource(gvr, namespace) + resourceClient := dynamicClient.Resource(gvr).Namespace(namespace) return definition, resourceClient } From 121b698492fcf546c9264b27d086b18d915c607f Mon Sep 17 00:00:00 2001 From: David Eads Date: Thu, 26 Apr 2018 15:36:07 -0400 Subject: [PATCH 2/2] add fake dynamic client --- .../src/k8s.io/client-go/dynamic/fake/BUILD | 3 + .../k8s.io/client-go/dynamic/fake/simple.go | 236 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 staging/src/k8s.io/client-go/dynamic/fake/simple.go diff --git a/staging/src/k8s.io/client-go/dynamic/fake/BUILD b/staging/src/k8s.io/client-go/dynamic/fake/BUILD index d006188f895..fa3b624da45 100644 --- a/staging/src/k8s.io/client-go/dynamic/fake/BUILD +++ b/staging/src/k8s.io/client-go/dynamic/fake/BUILD @@ -10,14 +10,17 @@ go_library( srcs = [ "client.go", "client_pool.go", + "simple.go", ], importpath = "k8s.io/client-go/dynamic/fake", deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_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/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", diff --git a/staging/src/k8s.io/client-go/dynamic/fake/simple.go b/staging/src/k8s.io/client-go/dynamic/fake/simple.go new file mode 100644 index 00000000000..9aafb81a457 --- /dev/null +++ b/staging/src/k8s.io/client-go/dynamic/fake/simple.go @@ -0,0 +1,236 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 fake + +import ( + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/testing" +) + +func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient { + codecs := serializer.NewCodecFactory(scheme) + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &FakeDynamicClient{} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type FakeDynamicClient struct { + testing.Fake + scheme *runtime.Scheme +} + +type dynamicResourceClient struct { + client *FakeDynamicClient + namespace string + resource schema.GroupVersionResource + subresource string +} + +var _ dynamic.DynamicInterface = &FakeDynamicClient{} + +func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableDynamicResourceInterface { + return &dynamicResourceClient{client: c, resource: resource} +} + +// Deprecated, this isn't how we want to do it +func (c *FakeDynamicClient) ClusterSubresource(resource schema.GroupVersionResource, subresource string) dynamic.DynamicResourceInterface { + return &dynamicResourceClient{client: c, resource: resource, subresource: subresource} +} + +// Deprecated, this isn't how we want to do it +func (c *FakeDynamicClient) NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) dynamic.DynamicResourceInterface { + return &dynamicResourceClient{client: c, resource: resource, namespace: namespace, subresource: subresource} +} + +func (c *dynamicResourceClient) Namespace(ns string) dynamic.DynamicResourceInterface { + ret := *c + ret.namespace = ns + return &ret +} + +func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + uncastRet, err := c.client.Fake. + Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + uncastRet, err := c.client.Fake. + Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + uncastRet, err := c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions) error { + _, err := c.client.Fake. + Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) + + return err +} + +func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error { + action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) + + _, err := c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) + return err +} + +func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { + uncastRet, err := c.client.Fake. + Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"}) + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { + obj, err := c.client.Fake. + Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + + retUnstructured := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(obj, retUnstructured, nil); err != nil { + return nil, err + } + entireList, err := retUnstructured.ToList() + if err != nil { + return nil, err + } + + list := &unstructured.UnstructuredList{} + for _, item := range entireList.Items { + metadata, err := meta.Accessor(item) + if err != nil { + return nil, err + } + if label.Matches(labels.Set(metadata.GetLabels())) { + list.Items = append(list.Items, item) + } + } + return list, nil +} + +func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + return c.client.Fake. + InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) +} + +func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) { + uncastRet, err := c.client.Fake. + Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +}