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 metadatainformer
18
19import (
20	"context"
21	"flag"
22	"testing"
23	"time"
24
25	"k8s.io/klog"
26
27	"k8s.io/apimachinery/pkg/api/equality"
28	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29	"k8s.io/apimachinery/pkg/runtime"
30	"k8s.io/apimachinery/pkg/runtime/schema"
31	"k8s.io/apimachinery/pkg/util/diff"
32	"k8s.io/client-go/metadata/fake"
33	"k8s.io/client-go/tools/cache"
34)
35
36func init() {
37	klog.InitFlags(flag.CommandLine)
38	flag.CommandLine.Lookup("v").Value.Set("5")
39	flag.CommandLine.Lookup("alsologtostderr").Value.Set("true")
40}
41
42func TestMetadataSharedInformerFactory(t *testing.T) {
43	scenarios := []struct {
44		name        string
45		existingObj *metav1.PartialObjectMetadata
46		gvr         schema.GroupVersionResource
47		ns          string
48		trigger     func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata
49		handler     func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs
50	}{
51		// scenario 1
52		{
53			name: "scenario 1: test if adding an object triggers AddFunc",
54			ns:   "ns-foo",
55			gvr:  schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
56			trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, _ *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
57				testObject := newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo")
58				createdObj, err := fakeClient.Resource(gvr).Namespace(ns).(fake.MetadataClient).CreateFake(testObject, metav1.CreateOptions{})
59				if err != nil {
60					t.Error(err)
61				}
62				return createdObj
63			},
64			handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
65				return &cache.ResourceEventHandlerFuncs{
66					AddFunc: func(obj interface{}) {
67						rcvCh <- obj.(*metav1.PartialObjectMetadata)
68					},
69				}
70			},
71		},
72
73		// scenario 2
74		{
75			name:        "scenario 2: tests if updating an object triggers UpdateFunc",
76			ns:          "ns-foo",
77			gvr:         schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
78			existingObj: newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
79			trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
80				if testObject.Annotations == nil {
81					testObject.Annotations = make(map[string]string)
82				}
83				testObject.Annotations["test"] = "updatedName"
84				updatedObj, err := fakeClient.Resource(gvr).Namespace(ns).(fake.MetadataClient).UpdateFake(testObject, metav1.UpdateOptions{})
85				if err != nil {
86					t.Error(err)
87				}
88				return updatedObj
89			},
90			handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
91				return &cache.ResourceEventHandlerFuncs{
92					UpdateFunc: func(old, updated interface{}) {
93						rcvCh <- updated.(*metav1.PartialObjectMetadata)
94					},
95				}
96			},
97		},
98
99		// scenario 3
100		{
101			name:        "scenario 3: test if deleting an object triggers DeleteFunc",
102			ns:          "ns-foo",
103			gvr:         schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
104			existingObj: newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
105			trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
106				err := fakeClient.Resource(gvr).Namespace(ns).Delete(context.TODO(), testObject.GetName(), metav1.DeleteOptions{})
107				if err != nil {
108					t.Error(err)
109				}
110				return testObject
111			},
112			handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
113				return &cache.ResourceEventHandlerFuncs{
114					DeleteFunc: func(obj interface{}) {
115						rcvCh <- obj.(*metav1.PartialObjectMetadata)
116					},
117				}
118			},
119		},
120	}
121
122	for _, ts := range scenarios {
123		t.Run(ts.name, func(t *testing.T) {
124			// test data
125			timeout := time.Duration(3 * time.Second)
126			ctx, cancel := context.WithTimeout(context.Background(), timeout)
127			defer cancel()
128			scheme := runtime.NewScheme()
129			metav1.AddMetaToScheme(scheme)
130			informerReciveObjectCh := make(chan *metav1.PartialObjectMetadata, 1)
131			objs := []runtime.Object{}
132			if ts.existingObj != nil {
133				objs = append(objs, ts.existingObj)
134			}
135			fakeClient := fake.NewSimpleMetadataClient(scheme, objs...)
136			target := NewSharedInformerFactory(fakeClient, 0)
137
138			// act
139			informerListerForGvr := target.ForResource(ts.gvr)
140			informerListerForGvr.Informer().AddEventHandler(ts.handler(informerReciveObjectCh))
141			target.Start(ctx.Done())
142			if synced := target.WaitForCacheSync(ctx.Done()); !synced[ts.gvr] {
143				t.Fatalf("informer for %s hasn't synced", ts.gvr)
144			}
145
146			testObject := ts.trigger(ts.gvr, ts.ns, fakeClient, ts.existingObj)
147			select {
148			case objFromInformer := <-informerReciveObjectCh:
149				if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
150					t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
151				}
152			case <-ctx.Done():
153				t.Errorf("tested informer haven't received an object, waited %v", timeout)
154			}
155		})
156	}
157}
158
159func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
160	return &metav1.PartialObjectMetadata{
161		TypeMeta: metav1.TypeMeta{
162			APIVersion: apiVersion,
163			Kind:       kind,
164		},
165		ObjectMeta: metav1.ObjectMeta{
166			Namespace: namespace,
167			Name:      name,
168		},
169	}
170}
171