1/* 2Copyright 2018 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package dynamicinformer_test 18 19import ( 20 "context" 21 "testing" 22 "time" 23 24 "k8s.io/apimachinery/pkg/api/equality" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/diff" 30 "k8s.io/client-go/dynamic/dynamicinformer" 31 "k8s.io/client-go/dynamic/fake" 32 "k8s.io/client-go/tools/cache" 33) 34 35func TestDynamicSharedInformerFactory(t *testing.T) { 36 scenarios := []struct { 37 name string 38 existingObj *unstructured.Unstructured 39 gvr schema.GroupVersionResource 40 ns string 41 trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured 42 handler func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs 43 }{ 44 // scenario 1 45 { 46 name: "scenario 1: test if adding an object triggers AddFunc", 47 ns: "ns-foo", 48 gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}, 49 trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, _ *unstructured.Unstructured) *unstructured.Unstructured { 50 testObject := newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo") 51 createdObj, err := fakeClient.Resource(gvr).Namespace(ns).Create(testObject, metav1.CreateOptions{}) 52 if err != nil { 53 t.Error(err) 54 } 55 return createdObj 56 }, 57 handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs { 58 return &cache.ResourceEventHandlerFuncs{ 59 AddFunc: func(obj interface{}) { 60 rcvCh <- obj.(*unstructured.Unstructured) 61 }, 62 } 63 }, 64 }, 65 66 // scenario 2 67 { 68 name: "scenario 2: tests if updating an object triggers UpdateFunc", 69 ns: "ns-foo", 70 gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}, 71 existingObj: newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"), 72 trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured { 73 testObject.Object["spec"] = "updatedName" 74 updatedObj, err := fakeClient.Resource(gvr).Namespace(ns).Update(testObject, metav1.UpdateOptions{}) 75 if err != nil { 76 t.Error(err) 77 } 78 return updatedObj 79 }, 80 handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs { 81 return &cache.ResourceEventHandlerFuncs{ 82 UpdateFunc: func(old, updated interface{}) { 83 rcvCh <- updated.(*unstructured.Unstructured) 84 }, 85 } 86 }, 87 }, 88 89 // scenario 3 90 { 91 name: "scenario 3: test if deleting an object triggers DeleteFunc", 92 ns: "ns-foo", 93 gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}, 94 existingObj: newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"), 95 trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured { 96 err := fakeClient.Resource(gvr).Namespace(ns).Delete(testObject.GetName(), &metav1.DeleteOptions{}) 97 if err != nil { 98 t.Error(err) 99 } 100 return testObject 101 }, 102 handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs { 103 return &cache.ResourceEventHandlerFuncs{ 104 DeleteFunc: func(obj interface{}) { 105 rcvCh <- obj.(*unstructured.Unstructured) 106 }, 107 } 108 }, 109 }, 110 } 111 112 for _, ts := range scenarios { 113 t.Run(ts.name, func(t *testing.T) { 114 // test data 115 timeout := time.Duration(3 * time.Second) 116 ctx, cancel := context.WithTimeout(context.Background(), timeout) 117 defer cancel() 118 scheme := runtime.NewScheme() 119 informerReciveObjectCh := make(chan *unstructured.Unstructured, 1) 120 objs := []runtime.Object{} 121 if ts.existingObj != nil { 122 objs = append(objs, ts.existingObj) 123 } 124 fakeClient := fake.NewSimpleDynamicClient(scheme, objs...) 125 target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0) 126 127 // act 128 informerListerForGvr := target.ForResource(ts.gvr) 129 informerListerForGvr.Informer().AddEventHandler(ts.handler(informerReciveObjectCh)) 130 target.Start(ctx.Done()) 131 if synced := target.WaitForCacheSync(ctx.Done()); !synced[ts.gvr] { 132 t.Errorf("informer for %s hasn't synced", ts.gvr) 133 } 134 135 testObject := ts.trigger(ts.gvr, ts.ns, fakeClient, ts.existingObj) 136 select { 137 case objFromInformer := <-informerReciveObjectCh: 138 if !equality.Semantic.DeepEqual(testObject, objFromInformer) { 139 t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer)) 140 } 141 case <-ctx.Done(): 142 t.Errorf("tested informer haven't received an object, waited %v", timeout) 143 } 144 }) 145 } 146} 147 148func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured { 149 return &unstructured.Unstructured{ 150 Object: map[string]interface{}{ 151 "apiVersion": apiVersion, 152 "kind": kind, 153 "metadata": map[string]interface{}{ 154 "namespace": namespace, 155 "name": name, 156 }, 157 "spec": name, 158 }, 159 } 160} 161