add GVK to fake dynamic client to match actual behavior

Kubernetes-commit: f4383458432cd67714e9ce0acde56a2ed5c24a21
This commit is contained in:
David Eads 2020-10-29 15:53:34 -04:00 committed by Kubernetes Publisher
parent 75ef13ec60
commit 20c034c178
3 changed files with 135 additions and 14 deletions

View File

@ -95,7 +95,12 @@ func TestFilteredDynamicSharedInformerFactory(t *testing.T) {
if ts.existingObj != nil { if ts.existingObj != nil {
objs = append(objs, ts.existingObj) objs = append(objs, ts.existingObj)
} }
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...) // don't adjust the scheme to include deploymentlist. This is testing whether an informer can be created against using
// a client that doesn't have a type registered in the scheme.
gvrToListKind := map[schema.GroupVersionResource]string{
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentList",
}
fakeClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objs...)
target := dynamicinformer.NewFilteredDynamicSharedInformerFactory(fakeClient, 0, ts.informNS, nil) target := dynamicinformer.NewFilteredDynamicSharedInformerFactory(fakeClient, 0, ts.informNS, nil)
// act // act
@ -214,7 +219,12 @@ func TestDynamicSharedInformerFactory(t *testing.T) {
if ts.existingObj != nil { if ts.existingObj != nil {
objs = append(objs, ts.existingObj) objs = append(objs, ts.existingObj)
} }
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...) // don't adjust the scheme to include deploymentlist. This is testing whether an informer can be created against using
// a client that doesn't have a type registered in the scheme.
gvrToListKind := map[schema.GroupVersionResource]string{
{Group: "extensions", Version: "v1beta1", Resource: "deployments"}: "DeploymentList",
}
fakeClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objs...)
target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0) target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
// act // act

View File

@ -18,6 +18,7 @@ package fake
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -34,11 +35,45 @@ import (
) )
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient { func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
// In order to use List with this client, you have to have the v1.List registered in your scheme. Neat thing though return NewSimpleDynamicClientWithCustomListKinds(scheme, nil, objects...)
// it does NOT have to be the *same* list. UnstructuredList returned from this fake client will NOT have apiVersion and kind set, }
// but each Unstructured object in Items will preserve their respective apiVersion and kind. As a result, schema conversion for
// *List kinds will not work and conversion of each Unstructured object in Items will be required instead. // NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "List"}, &unstructured.UnstructuredList{}) // and allow the default guessing for resources match. Sometimes that doesn't work, so you can specify a custom mapping here.
func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient {
// In order to use List with this client, you have to have your lists registered so that the object tracker will find them
// in the scheme to support the t.scheme.New(listGVK) call when it's building the return value.
// Since the base fake client needs the listGVK passed through the action (in cases where there are no instances, it
// cannot look up the actual hits), we need to know a mapping of GVR to listGVK here. For GETs and other types of calls,
// there is no return value that contains a GVK, so it doesn't have to know the mapping in advance.
// first we attempt to invert known List types from the scheme to auto guess the resource with unsafe guesses
// this covers common usage of registering types in scheme and passing them
completeGVRToListKind := map[schema.GroupVersionResource]string{}
for listGVK := range scheme.AllKnownTypes() {
if !strings.HasSuffix(listGVK.Kind, "List") {
continue
}
nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4])
plural, _ := meta.UnsafeGuessKindToResource(nonListGVK)
completeGVRToListKind[plural] = listGVK.Kind
}
for gvr, listKind := range gvrToListKind {
if !strings.HasSuffix(listKind, "List") {
panic("coding error, listGVK must end in List or this fake client doesn't work right")
}
listGVK := gvr.GroupVersion().WithKind(listKind)
// if we already have this type registered, just skip it
if _, err := scheme.New(listGVK); err == nil {
completeGVRToListKind[gvr] = listKind
continue
}
scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
completeGVRToListKind[gvr] = listKind
}
codecs := serializer.NewCodecFactory(scheme) codecs := serializer.NewCodecFactory(scheme)
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
@ -48,7 +83,7 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
} }
} }
cs := &FakeDynamicClient{scheme: scheme} cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind}
cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource() gvr := action.GetResource()
@ -69,18 +104,20 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
type FakeDynamicClient struct { type FakeDynamicClient struct {
testing.Fake testing.Fake
scheme *runtime.Scheme scheme *runtime.Scheme
gvrToListKind map[schema.GroupVersionResource]string
} }
type dynamicResourceClient struct { type dynamicResourceClient struct {
client *FakeDynamicClient client *FakeDynamicClient
namespace string namespace string
resource schema.GroupVersionResource resource schema.GroupVersionResource
listKind string
} }
var _ dynamic.Interface = &FakeDynamicClient{} var _ dynamic.Interface = &FakeDynamicClient{}
func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource} return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]}
} }
func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface { func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
@ -276,16 +313,22 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
} }
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
if len(c.listKind) == 0 {
panic(fmt.Sprintf("coding error: you must register resource to list kind for every resource you're going to LIST when creating the client. See NewSimpleDynamicClientWithCustomListKinds or register the list into the scheme: %v out of %v", c.resource, c.client.gvrToListKind))
}
listGVK := c.resource.GroupVersion().WithKind(c.listKind)
listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4]) /*base library appends List*/
var obj runtime.Object var obj runtime.Object
var err error var err error
switch { switch {
case len(c.namespace) == 0: case len(c.namespace) == 0:
obj, err = c.client.Fake. obj, err = c.client.Fake.
Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "dynamic list fail"}) Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"})
case len(c.namespace) > 0: case len(c.namespace) > 0:
obj, err = c.client.Fake. obj, err = c.client.Fake.
Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"}) Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
} }
@ -309,6 +352,7 @@ func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOption
list := &unstructured.UnstructuredList{} list := &unstructured.UnstructuredList{}
list.SetResourceVersion(entireList.GetResourceVersion()) list.SetResourceVersion(entireList.GetResourceVersion())
list.GetObjectKind().SetGroupVersionKind(listGVK)
for i := range entireList.Items { for i := range entireList.Items {
item := &entireList.Items[i] item := &entireList.Items[i]
metadata, err := meta.Accessor(item) metadata, err := meta.Accessor(item)

View File

@ -59,10 +59,74 @@ func newUnstructuredWithSpec(spec map[string]interface{}) *unstructured.Unstruct
return u return u
} }
func TestGet(t *testing.T) {
scheme := runtime.NewScheme()
client := NewSimpleDynamicClient(scheme, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"))
get, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).Namespace("ns-foo").Get(context.TODO(), "name-foo", metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
expected := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "group/version",
"kind": "TheKind",
"metadata": map[string]interface{}{
"name": "name-foo",
"namespace": "ns-foo",
},
},
}
if !equality.Semantic.DeepEqual(get, expected) {
t.Fatal(diff.ObjectGoPrintDiff(expected, get))
}
}
func TestListDecoding(t *testing.T) {
// this the duplication of logic from the real List API. This will prove that our dynamic client actually returns the gvk
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, []byte(`{"apiVersion": "group/version", "kind": "TheKindList", "items":[]}`))
if err != nil {
t.Fatal(err)
}
list := uncastObj.(*unstructured.UnstructuredList)
expectedList := &unstructured.UnstructuredList{
Object: map[string]interface{}{
"apiVersion": "group/version",
"kind": "TheKindList",
},
Items: []unstructured.Unstructured{},
}
if !equality.Semantic.DeepEqual(list, expectedList) {
t.Fatal(diff.ObjectGoPrintDiff(expectedList, list))
}
}
func TestGetDecoding(t *testing.T) {
// this the duplication of logic from the real Get API. This will prove that our dynamic client actually returns the gvk
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, []byte(`{"apiVersion": "group/version", "kind": "TheKind"}`))
if err != nil {
t.Fatal(err)
}
get := uncastObj.(*unstructured.Unstructured)
expectedObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "group/version",
"kind": "TheKind",
},
}
if !equality.Semantic.DeepEqual(get, expectedObj) {
t.Fatal(diff.ObjectGoPrintDiff(expectedObj, get))
}
}
func TestList(t *testing.T) { func TestList(t *testing.T) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
client := NewSimpleDynamicClient(scheme, client := NewSimpleDynamicClientWithCustomListKinds(scheme,
map[schema.GroupVersionResource]string{
{Group: "group", Version: "version", Resource: "thekinds"}: "TheKindList",
},
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"), newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"), newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
@ -87,7 +151,10 @@ func TestList(t *testing.T) {
func Test_ListKind(t *testing.T) { func Test_ListKind(t *testing.T) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
client := NewSimpleDynamicClient(scheme, client := NewSimpleDynamicClientWithCustomListKinds(scheme,
map[schema.GroupVersionResource]string{
{Group: "group", Version: "version", Resource: "thekinds"}: "TheKindList",
},
&unstructured.UnstructuredList{ &unstructured.UnstructuredList{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "group/version", "apiVersion": "group/version",