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