1
16
17 package testing
18
19 import (
20 "fmt"
21 "math/rand"
22 "strconv"
23 "sync"
24 "testing"
25
26 "github.com/stretchr/testify/assert"
27
28 "k8s.io/apimachinery/pkg/api/errors"
29 "k8s.io/apimachinery/pkg/api/meta"
30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31 runtime "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/runtime/schema"
33 serializer "k8s.io/apimachinery/pkg/runtime/serializer"
34 "k8s.io/apimachinery/pkg/types"
35 "k8s.io/apimachinery/pkg/watch"
36 )
37
38 func getArbitraryResource(s schema.GroupVersionResource, name, namespace string) *unstructured.Unstructured {
39 return &unstructured.Unstructured{
40 Object: map[string]interface{}{
41 "kind": s.Resource,
42 "apiVersion": s.Version,
43 "metadata": map[string]interface{}{
44 "name": name,
45 "namespace": namespace,
46 "generateName": "test_generateName",
47 "uid": "test_uid",
48 "resourceVersion": "test_resourceVersion",
49 },
50 "data": strconv.Itoa(rand.Int()),
51 },
52 }
53 }
54
55 func TestWatchCallNonNamespace(t *testing.T) {
56 testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
57 testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
58 accessor, err := meta.Accessor(testObj)
59 if err != nil {
60 t.Fatalf("unexpected error: %v", err)
61 }
62 ns := accessor.GetNamespace()
63 scheme := runtime.NewScheme()
64 codecs := serializer.NewCodecFactory(scheme)
65 o := NewObjectTracker(scheme, codecs.UniversalDecoder())
66 watch, err := o.Watch(testResource, ns)
67 if err != nil {
68 t.Fatalf("test resource watch failed in %s: %v ", ns, err)
69 }
70 go func() {
71 err := o.Create(testResource, testObj, ns)
72 if err != nil {
73 t.Errorf("test resource creation failed: %v", err)
74 }
75 }()
76 out := <-watch.ResultChan()
77 assert.Equal(t, testObj, out.Object, "watched object mismatch")
78 }
79
80 func TestWatchCallAllNamespace(t *testing.T) {
81 testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
82 testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
83 accessor, err := meta.Accessor(testObj)
84 if err != nil {
85 t.Fatalf("unexpected error: %v", err)
86 }
87 ns := accessor.GetNamespace()
88 scheme := runtime.NewScheme()
89 codecs := serializer.NewCodecFactory(scheme)
90 o := NewObjectTracker(scheme, codecs.UniversalDecoder())
91 w, err := o.Watch(testResource, "test_namespace")
92 if err != nil {
93 t.Fatalf("test resource watch failed in test_namespace: %v", err)
94 }
95 wAll, err := o.Watch(testResource, "")
96 if err != nil {
97 t.Fatalf("test resource watch failed in all namespaces: %v", err)
98 }
99 go func() {
100 err := o.Create(testResource, testObj, ns)
101 assert.NoError(t, err, "test resource creation failed")
102 }()
103 out := <-w.ResultChan()
104 outAll := <-wAll.ResultChan()
105 assert.Equal(t, watch.Added, out.Type, "watch event mismatch")
106 assert.Equal(t, watch.Added, outAll.Type, "watch event mismatch")
107 assert.Equal(t, testObj, out.Object, "watched created object mismatch")
108 assert.Equal(t, testObj, outAll.Object, "watched created object mismatch")
109 go func() {
110 err := o.Update(testResource, testObj, ns)
111 assert.NoError(t, err, "test resource updating failed")
112 }()
113 out = <-w.ResultChan()
114 outAll = <-wAll.ResultChan()
115 assert.Equal(t, watch.Modified, out.Type, "watch event mismatch")
116 assert.Equal(t, watch.Modified, outAll.Type, "watch event mismatch")
117 assert.Equal(t, testObj, out.Object, "watched updated object mismatch")
118 assert.Equal(t, testObj, outAll.Object, "watched updated object mismatch")
119 go func() {
120 err := o.Delete(testResource, "test_namespace", "test_name")
121 assert.NoError(t, err, "test resource deletion failed")
122 }()
123 out = <-w.ResultChan()
124 outAll = <-wAll.ResultChan()
125 assert.Equal(t, watch.Deleted, out.Type, "watch event mismatch")
126 assert.Equal(t, watch.Deleted, outAll.Type, "watch event mismatch")
127 assert.Equal(t, testObj, out.Object, "watched deleted object mismatch")
128 assert.Equal(t, testObj, outAll.Object, "watched deleted object mismatch")
129 }
130
131 func TestWatchCallMultipleInvocation(t *testing.T) {
132 cases := []struct {
133 name string
134 op watch.EventType
135 ns string
136 }{
137 {
138 "foo",
139 watch.Added,
140 "test_namespace",
141 },
142 {
143 "bar",
144 watch.Added,
145 "test_namespace",
146 },
147 {
148 "baz",
149 watch.Added,
150 "",
151 },
152 {
153 "bar",
154 watch.Modified,
155 "test_namespace",
156 },
157 {
158 "baz",
159 watch.Modified,
160 "",
161 },
162 {
163 "foo",
164 watch.Deleted,
165 "test_namespace",
166 },
167 {
168 "bar",
169 watch.Deleted,
170 "test_namespace",
171 },
172 {
173 "baz",
174 watch.Deleted,
175 "",
176 },
177 }
178
179 scheme := runtime.NewScheme()
180 codecs := serializer.NewCodecFactory(scheme)
181 testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
182
183 o := NewObjectTracker(scheme, codecs.UniversalDecoder())
184 watchNamespaces := []string{
185 "",
186 "",
187 "test_namespace",
188 "test_namespace",
189 }
190 var wg sync.WaitGroup
191 wg.Add(len(watchNamespaces))
192 for idx, watchNamespace := range watchNamespaces {
193 i := idx
194 watchNamespace := watchNamespace
195 w, err := o.Watch(testResource, watchNamespace)
196 if err != nil {
197 t.Fatalf("test resource watch failed in %s: %v", watchNamespace, err)
198 }
199 go func() {
200 assert.NoError(t, err, "watch invocation failed")
201 for _, c := range cases {
202 if watchNamespace == "" || c.ns == watchNamespace {
203 fmt.Printf("%#v %#v\n", c, i)
204 event := <-w.ResultChan()
205 accessor, err := meta.Accessor(event.Object)
206 if err != nil {
207 t.Errorf("unexpected error: %v", err)
208 break
209 }
210 assert.Equal(t, c.op, event.Type, "watch event mismatched")
211 assert.Equal(t, c.name, accessor.GetName(), "watched object mismatch")
212 assert.Equal(t, c.ns, accessor.GetNamespace(), "watched object mismatch")
213 }
214 }
215 wg.Done()
216 }()
217 }
218 for _, c := range cases {
219 switch c.op {
220 case watch.Added:
221 obj := getArbitraryResource(testResource, c.name, c.ns)
222 o.Create(testResource, obj, c.ns)
223 case watch.Modified:
224 obj := getArbitraryResource(testResource, c.name, c.ns)
225 o.Update(testResource, obj, c.ns)
226 case watch.Deleted:
227 o.Delete(testResource, c.ns, c.name)
228 }
229 }
230 wg.Wait()
231 }
232
233 func TestWatchAddAfterStop(t *testing.T) {
234 testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
235 testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
236 accessor, err := meta.Accessor(testObj)
237 if err != nil {
238 t.Fatalf("unexpected error: %v", err)
239 }
240
241 ns := accessor.GetNamespace()
242 scheme := runtime.NewScheme()
243 codecs := serializer.NewCodecFactory(scheme)
244 o := NewObjectTracker(scheme, codecs.UniversalDecoder())
245 watch, err := o.Watch(testResource, ns)
246 if err != nil {
247 t.Errorf("watch creation failed: %v", err)
248 }
249
250
251 defer func() {
252 if r := recover(); r != nil {
253 t.Errorf("Watch panicked when it should have ignored create after stop: %v", r)
254 }
255 }()
256
257 watch.Stop()
258 err = o.Create(testResource, testObj, ns)
259 if err != nil {
260 t.Errorf("test resource creation failed: %v", err)
261 }
262 }
263
264 func TestPatchWithMissingObject(t *testing.T) {
265 nodesResource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"}
266
267 scheme := runtime.NewScheme()
268 codecs := serializer.NewCodecFactory(scheme)
269 o := NewObjectTracker(scheme, codecs.UniversalDecoder())
270 reaction := ObjectReaction(o)
271 action := NewRootPatchSubresourceAction(nodesResource, "node-1", types.StrategicMergePatchType, []byte(`{}`))
272 handled, node, err := reaction(action)
273 assert.True(t, handled)
274 assert.Nil(t, node)
275 assert.EqualError(t, err, `nodes "node-1" not found`)
276 }
277
278 func TestGetWithExactMatch(t *testing.T) {
279 scheme := runtime.NewScheme()
280 codecs := serializer.NewCodecFactory(scheme)
281
282 constructObject := func(s schema.GroupVersionResource, name, namespace string) (*unstructured.Unstructured, schema.GroupVersionResource) {
283 obj := getArbitraryResource(s, name, namespace)
284 gvks, _, err := scheme.ObjectKinds(obj)
285 assert.NoError(t, err)
286 gvr, _ := meta.UnsafeGuessKindToResource(gvks[0])
287 return obj, gvr
288 }
289
290 var err error
291
292 o := NewObjectTracker(scheme, codecs.UniversalDecoder())
293 nodeResource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "node"}
294 node, gvr := constructObject(nodeResource, "node", "")
295
296 assert.Nil(t, o.Add(node))
297
298
299 _, err = o.Get(gvr, "", "node")
300 assert.NoError(t, err)
301
302
303 _, err = o.Get(gvr, "ns", "node")
304 assert.Error(t, err)
305 errNotFound := errors.NewNotFound(gvr.GroupResource(), "node")
306 assert.EqualError(t, err, errNotFound.Error())
307
308
309 o = NewObjectTracker(scheme, codecs.UniversalDecoder())
310 podResource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"}
311 pod, gvr := constructObject(podResource, "pod", "default")
312 assert.Nil(t, o.Add(pod))
313
314
315 _, err = o.Get(gvr, "default", "pod")
316 assert.NoError(t, err)
317
318
319 _, err = o.Get(gvr, "", "pod")
320 assert.Error(t, err)
321 errNotFound = errors.NewNotFound(gvr.GroupResource(), "pod")
322 assert.EqualError(t, err, errNotFound.Error())
323 }
324
325 func Test_resourceCovers(t *testing.T) {
326 type args struct {
327 resource string
328 action Action
329 }
330 tests := []struct {
331 name string
332 args args
333 want bool
334 }{
335 {
336 args: args{
337 resource: "*",
338 action: ActionImpl{},
339 },
340 want: true,
341 },
342 {
343 args: args{
344 resource: "serviceaccounts",
345 action: ActionImpl{},
346 },
347 want: false,
348 },
349 {
350 args: args{
351 resource: "serviceaccounts",
352 action: ActionImpl{
353 Resource: schema.GroupVersionResource{
354 Resource: "serviceaccounts",
355 },
356 },
357 },
358 want: true,
359 },
360 {
361 args: args{
362 resource: "serviceaccounts/token",
363 action: ActionImpl{
364 Resource: schema.GroupVersionResource{},
365 },
366 },
367 want: false,
368 },
369 {
370 args: args{
371 resource: "serviceaccounts/token",
372 action: ActionImpl{
373 Resource: schema.GroupVersionResource{
374 Resource: "serviceaccounts",
375 },
376 },
377 },
378 want: false,
379 },
380 {
381 args: args{
382 resource: "serviceaccounts/token",
383 action: ActionImpl{
384 Resource: schema.GroupVersionResource{},
385 Subresource: "token",
386 },
387 },
388 want: false,
389 },
390 {
391 args: args{
392 resource: "serviceaccounts/token",
393 action: ActionImpl{
394 Resource: schema.GroupVersionResource{
395 Resource: "serviceaccounts",
396 },
397 Subresource: "token",
398 },
399 },
400 want: true,
401 },
402 }
403 for _, tt := range tests {
404 t.Run(tt.name, func(t *testing.T) {
405 if got := resourceCovers(tt.args.resource, tt.args.action); got != tt.want {
406 t.Errorf("resourceCovers() = %v, want %v", got, tt.want)
407 }
408 })
409 }
410 }
411
View as plain text