1
16
17 package controllerutil_test
18
19 import (
20 "context"
21 "fmt"
22 "math/rand"
23
24 . "github.com/onsi/ginkgo/v2"
25 . "github.com/onsi/gomega"
26 appsv1 "k8s.io/api/apps/v1"
27 corev1 "k8s.io/api/core/v1"
28 extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/types"
32 "k8s.io/client-go/kubernetes/scheme"
33 "k8s.io/utils/ptr"
34 "sigs.k8s.io/controller-runtime/pkg/client"
35 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
36 )
37
38 var _ = Describe("Controllerutil", func() {
39 Describe("SetOwnerReference", func() {
40 It("should set ownerRef on an empty list", func() {
41 rs := &appsv1.ReplicaSet{}
42 dep := &extensionsv1beta1.Deployment{
43 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
44 }
45 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
46 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
47 Name: "foo",
48 Kind: "Deployment",
49 APIVersion: "extensions/v1beta1",
50 UID: "foo-uid",
51 }))
52 })
53
54 It("should not duplicate owner references", func() {
55 rs := &appsv1.ReplicaSet{
56 ObjectMeta: metav1.ObjectMeta{
57 OwnerReferences: []metav1.OwnerReference{
58 {
59 Name: "foo",
60 Kind: "Deployment",
61 APIVersion: "extensions/v1beta1",
62 UID: "foo-uid",
63 },
64 },
65 },
66 }
67 dep := &extensionsv1beta1.Deployment{
68 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
69 }
70
71 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
72 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
73 Name: "foo",
74 Kind: "Deployment",
75 APIVersion: "extensions/v1beta1",
76 UID: "foo-uid",
77 }))
78 })
79
80 It("should update the reference", func() {
81 rs := &appsv1.ReplicaSet{
82 ObjectMeta: metav1.ObjectMeta{
83 OwnerReferences: []metav1.OwnerReference{
84 {
85 Name: "foo",
86 Kind: "Deployment",
87 APIVersion: "extensions/v1alpha1",
88 UID: "foo-uid-1",
89 },
90 },
91 },
92 }
93 dep := &extensionsv1beta1.Deployment{
94 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
95 }
96
97 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
98 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
99 Name: "foo",
100 Kind: "Deployment",
101 APIVersion: "extensions/v1beta1",
102 UID: "foo-uid-2",
103 }))
104 })
105 It("should remove the owner reference", func() {
106 rs := &appsv1.ReplicaSet{
107 ObjectMeta: metav1.ObjectMeta{
108 OwnerReferences: []metav1.OwnerReference{
109 {
110 Name: "foo",
111 Kind: "Deployment",
112 APIVersion: "extensions/v1alpha1",
113 UID: "foo-uid-1",
114 },
115 },
116 },
117 }
118 dep := &extensionsv1beta1.Deployment{
119 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
120 }
121
122 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
123 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
124 Name: "foo",
125 Kind: "Deployment",
126 APIVersion: "extensions/v1beta1",
127 UID: "foo-uid-2",
128 }))
129 Expect(controllerutil.RemoveOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
130 Expect(rs.GetOwnerReferences()).To(BeEmpty())
131 })
132 It("should remove the owner reference established by the SetControllerReference function", func() {
133 rs := &appsv1.ReplicaSet{}
134 dep := &extensionsv1beta1.Deployment{
135 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
136 }
137
138 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
139 t := true
140 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
141 Name: "foo",
142 Kind: "Deployment",
143 APIVersion: "extensions/v1beta1",
144 UID: "foo-uid",
145 Controller: &t,
146 BlockOwnerDeletion: &t,
147 }))
148 Expect(controllerutil.RemoveOwnerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
149 Expect(rs.GetOwnerReferences()).To(BeEmpty())
150 })
151 It("should error when trying to remove the reference that doesn't exist", func() {
152 rs := &appsv1.ReplicaSet{
153 ObjectMeta: metav1.ObjectMeta{},
154 }
155 dep := &extensionsv1beta1.Deployment{
156 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
157 }
158 Expect(controllerutil.RemoveOwnerReference(dep, rs, scheme.Scheme)).To(HaveOccurred())
159 Expect(rs.GetOwnerReferences()).To(BeEmpty())
160 })
161 It("should error when trying to remove the reference that doesn't abide by the scheme", func() {
162 rs := &appsv1.ReplicaSet{
163 ObjectMeta: metav1.ObjectMeta{},
164 }
165 dep := &extensionsv1beta1.Deployment{
166 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
167 }
168 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
169 Expect(controllerutil.RemoveOwnerReference(dep, rs, runtime.NewScheme())).To(HaveOccurred())
170 Expect(rs.GetOwnerReferences()).To(HaveLen(1))
171 })
172 It("should error when trying to remove the owner when setting the owner as a non runtime.Object", func() {
173 var obj metav1.Object
174 rs := &appsv1.ReplicaSet{
175 ObjectMeta: metav1.ObjectMeta{},
176 }
177 dep := &extensionsv1beta1.Deployment{
178 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
179 }
180 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
181 Expect(controllerutil.RemoveOwnerReference(obj, rs, scheme.Scheme)).To(HaveOccurred())
182 Expect(rs.GetOwnerReferences()).To(HaveLen(1))
183 })
184
185 It("should error when trying to remove an owner that doesn't exist", func() {
186 rs := &appsv1.ReplicaSet{
187 ObjectMeta: metav1.ObjectMeta{},
188 }
189 dep := &extensionsv1beta1.Deployment{
190 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
191 }
192 dep2 := &extensionsv1beta1.Deployment{
193 ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: "bar-uid-3"},
194 }
195 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
196 Expect(controllerutil.RemoveOwnerReference(dep2, rs, scheme.Scheme)).To(HaveOccurred())
197 Expect(rs.GetOwnerReferences()).To(HaveLen(1))
198 })
199
200 It("should return true when HasControllerReference evaluates owner reference set by SetControllerReference", func() {
201 rs := &appsv1.ReplicaSet{
202 ObjectMeta: metav1.ObjectMeta{},
203 }
204 dep := &extensionsv1beta1.Deployment{
205 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
206 }
207 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
208 Expect(controllerutil.HasControllerReference(rs)).To(BeTrue())
209 })
210
211 It("should return false when HasControllerReference evaluates owner reference set by SetOwnerReference", func() {
212 rs := &appsv1.ReplicaSet{
213 ObjectMeta: metav1.ObjectMeta{},
214 }
215 dep := &extensionsv1beta1.Deployment{
216 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
217 }
218 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
219 Expect(controllerutil.HasControllerReference(rs)).To(BeFalse())
220 })
221
222 It("should error when RemoveControllerReference owner's controller is set to false", func() {
223 rs := &appsv1.ReplicaSet{
224 ObjectMeta: metav1.ObjectMeta{},
225 }
226 dep := &extensionsv1beta1.Deployment{
227 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
228 }
229 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
230 Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).To(HaveOccurred())
231 })
232
233 It("should error when RemoveControllerReference passed in owner is not the owner", func() {
234 rs := &appsv1.ReplicaSet{
235 ObjectMeta: metav1.ObjectMeta{},
236 }
237 dep := &extensionsv1beta1.Deployment{
238 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
239 }
240 dep2 := &extensionsv1beta1.Deployment{
241 ObjectMeta: metav1.ObjectMeta{Name: "foo-2", UID: "foo-uid-42"},
242 }
243 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
244 Expect(controllerutil.SetOwnerReference(dep2, rs, scheme.Scheme)).ToNot(HaveOccurred())
245 Expect(controllerutil.RemoveControllerReference(dep2, rs, scheme.Scheme)).To(HaveOccurred())
246 Expect(rs.GetOwnerReferences()).To(HaveLen(2))
247 })
248
249 It("should not error when RemoveControllerReference owner's controller is set to true", func() {
250 rs := &appsv1.ReplicaSet{
251 ObjectMeta: metav1.ObjectMeta{},
252 }
253 dep := &extensionsv1beta1.Deployment{
254 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"},
255 }
256 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
257 Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
258 Expect(rs.GetOwnerReferences()).To(BeEmpty())
259 })
260 })
261
262 Describe("SetControllerReference", func() {
263 It("should set the OwnerReference if it can find the group version kind", func() {
264 rs := &appsv1.ReplicaSet{}
265 dep := &extensionsv1beta1.Deployment{
266 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
267 }
268
269 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
270 t := true
271 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
272 Name: "foo",
273 Kind: "Deployment",
274 APIVersion: "extensions/v1beta1",
275 UID: "foo-uid",
276 Controller: &t,
277 BlockOwnerDeletion: &t,
278 }))
279 })
280
281 It("should return an error if it can't find the group version kind of the owner", func() {
282 rs := &appsv1.ReplicaSet{}
283 dep := &extensionsv1beta1.Deployment{
284 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
285 }
286 Expect(controllerutil.SetControllerReference(dep, rs, runtime.NewScheme())).To(HaveOccurred())
287 })
288
289 It("should return an error if the owner isn't a runtime.Object", func() {
290 rs := &appsv1.ReplicaSet{}
291 Expect(controllerutil.SetControllerReference(&errMetaObj{}, rs, scheme.Scheme)).To(HaveOccurred())
292 })
293
294 It("should return an error if object is already owned by another controller", func() {
295 t := true
296 rsOwners := []metav1.OwnerReference{
297 {
298 Name: "bar",
299 Kind: "Deployment",
300 APIVersion: "extensions/v1beta1",
301 UID: "bar-uid",
302 Controller: &t,
303 BlockOwnerDeletion: &t,
304 },
305 }
306 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
307 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
308
309 err := controllerutil.SetControllerReference(dep, rs, scheme.Scheme)
310
311 Expect(err).To(HaveOccurred())
312 Expect(err).To(BeAssignableToTypeOf(&controllerutil.AlreadyOwnedError{}))
313 })
314
315 It("should not duplicate existing owner reference", func() {
316 f := false
317 t := true
318 rsOwners := []metav1.OwnerReference{
319 {
320 Name: "foo",
321 Kind: "Deployment",
322 APIVersion: "extensions/v1beta1",
323 UID: "foo-uid",
324 Controller: &f,
325 BlockOwnerDeletion: &t,
326 },
327 }
328 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
329 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
330
331 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
332 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
333 Name: "foo",
334 Kind: "Deployment",
335 APIVersion: "extensions/v1beta1",
336 UID: "foo-uid",
337 Controller: &t,
338 BlockOwnerDeletion: &t,
339 }))
340 })
341
342 It("should replace the owner reference if it's already present", func() {
343 t := true
344 rsOwners := []metav1.OwnerReference{
345 {
346 Name: "foo",
347 Kind: "Deployment",
348 APIVersion: "extensions/v1alpha1",
349 UID: "foo-uid",
350 Controller: &t,
351 BlockOwnerDeletion: &t,
352 },
353 }
354 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
355 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
356
357 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
358 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
359 Name: "foo",
360 Kind: "Deployment",
361 APIVersion: "extensions/v1beta1",
362 UID: "foo-uid",
363 Controller: &t,
364 BlockOwnerDeletion: &t,
365 }))
366 })
367
368 It("should return an error if it's setting a cross-namespace owner reference", func() {
369 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "namespace1"}}
370 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "namespace2", UID: "foo-uid"}}
371
372 err := controllerutil.SetControllerReference(dep, rs, scheme.Scheme)
373
374 Expect(err).To(HaveOccurred())
375 })
376
377 It("should return an error if it's owner is namespaced resource but dependant is cluster-scoped resource", func() {
378 pv := &corev1.PersistentVolume{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
379 pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
380
381 err := controllerutil.SetControllerReference(pod, pv, scheme.Scheme)
382
383 Expect(err).To(HaveOccurred())
384 })
385
386 It("should not return any error if the existing owner has a different version", func() {
387 f := false
388 t := true
389 rsOwners := []metav1.OwnerReference{
390 {
391 Name: "foo",
392 Kind: "Deployment",
393 APIVersion: "extensions/v1alpha1",
394 UID: "foo-uid",
395 Controller: &f,
396 BlockOwnerDeletion: &t,
397 },
398 }
399 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
400 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
401
402 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
403 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
404 Name: "foo",
405 Kind: "Deployment",
406
407 APIVersion: "extensions/v1beta1",
408 UID: "foo-uid",
409 Controller: &t,
410 BlockOwnerDeletion: &t,
411 }))
412 })
413 })
414
415 Describe("CreateOrUpdate", func() {
416 var deploy *appsv1.Deployment
417 var deplSpec appsv1.DeploymentSpec
418 var deplKey types.NamespacedName
419 var specr controllerutil.MutateFn
420
421 BeforeEach(func() {
422 deploy = &appsv1.Deployment{
423 ObjectMeta: metav1.ObjectMeta{
424 Name: fmt.Sprintf("deploy-%d", rand.Int31()),
425 Namespace: "default",
426 },
427 }
428
429 deplSpec = appsv1.DeploymentSpec{
430 Selector: &metav1.LabelSelector{
431 MatchLabels: map[string]string{"foo": "bar"},
432 },
433 Template: corev1.PodTemplateSpec{
434 ObjectMeta: metav1.ObjectMeta{
435 Labels: map[string]string{
436 "foo": "bar",
437 },
438 },
439 Spec: corev1.PodSpec{
440 Containers: []corev1.Container{
441 {
442 Name: "busybox",
443 Image: "busybox",
444 },
445 },
446 },
447 },
448 }
449
450 deplKey = types.NamespacedName{
451 Name: deploy.Name,
452 Namespace: deploy.Namespace,
453 }
454
455 specr = deploymentSpecr(deploy, deplSpec)
456 })
457
458 It("creates a new object if one doesn't exists", func() {
459 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr)
460
461 By("returning no error")
462 Expect(err).NotTo(HaveOccurred())
463
464 By("returning OperationResultCreated")
465 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
466
467 By("actually having the deployment created")
468 fetched := &appsv1.Deployment{}
469 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed())
470
471 By("being mutated by MutateFn")
472 Expect(fetched.Spec.Template.Spec.Containers).To(HaveLen(1))
473 Expect(fetched.Spec.Template.Spec.Containers[0].Name).To(Equal(deplSpec.Template.Spec.Containers[0].Name))
474 Expect(fetched.Spec.Template.Spec.Containers[0].Image).To(Equal(deplSpec.Template.Spec.Containers[0].Image))
475 })
476
477 It("updates existing object", func() {
478 var scale int32 = 2
479 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr)
480 Expect(err).NotTo(HaveOccurred())
481 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
482
483 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentScaler(deploy, scale))
484 By("returning no error")
485 Expect(err).NotTo(HaveOccurred())
486
487 By("returning OperationResultUpdated")
488 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdated))
489
490 By("actually having the deployment scaled")
491 fetched := &appsv1.Deployment{}
492 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed())
493 Expect(*fetched.Spec.Replicas).To(Equal(scale))
494 })
495
496 It("updates only changed objects", func() {
497 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr)
498
499 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
500 Expect(err).NotTo(HaveOccurred())
501
502 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentIdentity)
503 By("returning no error")
504 Expect(err).NotTo(HaveOccurred())
505
506 By("returning OperationResultNone")
507 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
508 })
509
510 It("errors when MutateFn changes object name on creation", func() {
511 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, func() error {
512 Expect(specr()).To(Succeed())
513 return deploymentRenamer(deploy)()
514 })
515
516 By("returning error")
517 Expect(err).To(HaveOccurred())
518
519 By("returning OperationResultNone")
520 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
521 })
522
523 It("errors when MutateFn renames an object", func() {
524 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr)
525
526 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
527 Expect(err).NotTo(HaveOccurred())
528
529 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentRenamer(deploy))
530
531 By("returning error")
532 Expect(err).To(HaveOccurred())
533
534 By("returning OperationResultNone")
535 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
536 })
537
538 It("errors when object namespace changes", func() {
539 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr)
540
541 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
542 Expect(err).NotTo(HaveOccurred())
543
544 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentNamespaceChanger(deploy))
545
546 By("returning error")
547 Expect(err).To(HaveOccurred())
548
549 By("returning OperationResultNone")
550 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
551 })
552
553 It("aborts immediately if there was an error initially retrieving the object", func() {
554 op, err := controllerutil.CreateOrUpdate(context.TODO(), errorReader{c}, deploy, func() error {
555 Fail("Mutation method should not run")
556 return nil
557 })
558
559 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
560 Expect(err).To(HaveOccurred())
561 })
562 })
563
564 Describe("CreateOrPatch", func() {
565 var deploy *appsv1.Deployment
566 var deplSpec appsv1.DeploymentSpec
567 var deplKey types.NamespacedName
568 var specr controllerutil.MutateFn
569
570 BeforeEach(func() {
571 deploy = &appsv1.Deployment{
572 ObjectMeta: metav1.ObjectMeta{
573 Name: fmt.Sprintf("deploy-%d", rand.Int31()),
574 Namespace: "default",
575 },
576 }
577
578 deplSpec = appsv1.DeploymentSpec{
579 Selector: &metav1.LabelSelector{
580 MatchLabels: map[string]string{"foo": "bar"},
581 },
582 Template: corev1.PodTemplateSpec{
583 ObjectMeta: metav1.ObjectMeta{
584 Labels: map[string]string{
585 "foo": "bar",
586 },
587 },
588 Spec: corev1.PodSpec{
589 Containers: []corev1.Container{
590 {
591 Name: "busybox",
592 Image: "busybox",
593 },
594 },
595 },
596 },
597 }
598
599 deplKey = types.NamespacedName{
600 Name: deploy.Name,
601 Namespace: deploy.Namespace,
602 }
603
604 specr = deploymentSpecr(deploy, deplSpec)
605 })
606
607 assertLocalDeployWasUpdated := func(fetched *appsv1.Deployment) {
608 By("local deploy object was updated during patch & has same spec, status, resource version as fetched")
609 if fetched == nil {
610 fetched = &appsv1.Deployment{}
611 ExpectWithOffset(1, c.Get(context.TODO(), deplKey, fetched)).To(Succeed())
612 }
613 ExpectWithOffset(1, fetched.ResourceVersion).To(Equal(deploy.ResourceVersion))
614 ExpectWithOffset(1, fetched.Spec).To(BeEquivalentTo(deploy.Spec))
615 ExpectWithOffset(1, fetched.Status).To(BeEquivalentTo(deploy.Status))
616 }
617
618 assertLocalDeployStatusWasUpdated := func(fetched *appsv1.Deployment) {
619 By("local deploy object was updated during patch & has same spec, status, resource version as fetched")
620 if fetched == nil {
621 fetched = &appsv1.Deployment{}
622 ExpectWithOffset(1, c.Get(context.TODO(), deplKey, fetched)).To(Succeed())
623 }
624 ExpectWithOffset(1, fetched.ResourceVersion).To(Equal(deploy.ResourceVersion))
625 ExpectWithOffset(1, *fetched.Spec.Replicas).To(BeEquivalentTo(int32(5)))
626 ExpectWithOffset(1, fetched.Status).To(BeEquivalentTo(deploy.Status))
627 ExpectWithOffset(1, len(fetched.Status.Conditions)).To(BeEquivalentTo(1))
628 }
629
630 It("creates a new object if one doesn't exists", func() {
631 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
632
633 By("returning no error")
634 Expect(err).NotTo(HaveOccurred())
635
636 By("returning OperationResultCreated")
637 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
638
639 By("actually having the deployment created")
640 fetched := &appsv1.Deployment{}
641 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed())
642
643 By("being mutated by MutateFn")
644 Expect(fetched.Spec.Template.Spec.Containers).To(HaveLen(1))
645 Expect(fetched.Spec.Template.Spec.Containers[0].Name).To(Equal(deplSpec.Template.Spec.Containers[0].Name))
646 Expect(fetched.Spec.Template.Spec.Containers[0].Image).To(Equal(deplSpec.Template.Spec.Containers[0].Image))
647 })
648
649 It("patches existing object", func() {
650 var scale int32 = 2
651 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
652 Expect(err).NotTo(HaveOccurred())
653 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
654
655 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentScaler(deploy, scale))
656 By("returning no error")
657 Expect(err).NotTo(HaveOccurred())
658
659 By("returning OperationResultUpdated")
660 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdated))
661
662 By("actually having the deployment scaled")
663 fetched := &appsv1.Deployment{}
664 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed())
665 Expect(*fetched.Spec.Replicas).To(Equal(scale))
666 assertLocalDeployWasUpdated(fetched)
667 })
668
669 It("patches only changed objects", func() {
670 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
671
672 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
673 Expect(err).NotTo(HaveOccurred())
674
675 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentIdentity)
676 By("returning no error")
677 Expect(err).NotTo(HaveOccurred())
678
679 By("returning OperationResultNone")
680 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
681
682 assertLocalDeployWasUpdated(nil)
683 })
684
685 It("patches only changed status", func() {
686 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
687
688 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
689 Expect(err).NotTo(HaveOccurred())
690
691 deployStatus := appsv1.DeploymentStatus{
692 ReadyReplicas: 1,
693 Replicas: 3,
694 }
695 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentStatusr(deploy, deployStatus))
696 By("returning no error")
697 Expect(err).NotTo(HaveOccurred())
698
699 By("returning OperationResultUpdatedStatusOnly")
700 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatusOnly))
701
702 assertLocalDeployWasUpdated(nil)
703 })
704
705 It("patches resource and status", func() {
706 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
707
708 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
709 Expect(err).NotTo(HaveOccurred())
710
711 replicas := int32(3)
712 deployStatus := appsv1.DeploymentStatus{
713 ReadyReplicas: 1,
714 Replicas: replicas,
715 }
716 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error {
717 Expect(deploymentScaler(deploy, replicas)()).To(Succeed())
718 return deploymentStatusr(deploy, deployStatus)()
719 })
720 By("returning no error")
721 Expect(err).NotTo(HaveOccurred())
722
723 By("returning OperationResultUpdatedStatus")
724 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatus))
725
726 assertLocalDeployWasUpdated(nil)
727 })
728
729 It("patches resource and not empty status", func() {
730 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
731
732 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
733 Expect(err).NotTo(HaveOccurred())
734
735 replicas := int32(3)
736 deployStatus := appsv1.DeploymentStatus{
737 ReadyReplicas: 1,
738 Replicas: replicas,
739 }
740 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error {
741 Expect(deploymentScaler(deploy, replicas)()).To(Succeed())
742 return deploymentStatusr(deploy, deployStatus)()
743 })
744 By("returning no error")
745 Expect(err).NotTo(HaveOccurred())
746
747 By("returning OperationResultUpdatedStatus")
748 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatus))
749
750 assertLocalDeployWasUpdated(nil)
751
752 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error {
753 deploy.Spec.Replicas = ptr.To(int32(5))
754 deploy.Status.Conditions = []appsv1.DeploymentCondition{{
755 Type: appsv1.DeploymentProgressing,
756 Status: corev1.ConditionTrue,
757 }}
758 return nil
759 })
760 By("returning no error")
761 Expect(err).NotTo(HaveOccurred())
762
763 By("returning OperationResultUpdatedStatus")
764 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatus))
765
766 assertLocalDeployStatusWasUpdated(nil)
767 })
768
769 It("errors when MutateFn changes object name on creation", func() {
770 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error {
771 Expect(specr()).To(Succeed())
772 return deploymentRenamer(deploy)()
773 })
774
775 By("returning error")
776 Expect(err).To(HaveOccurred())
777
778 By("returning OperationResultNone")
779 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
780 })
781
782 It("errors when MutateFn renames an object", func() {
783 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
784
785 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
786 Expect(err).NotTo(HaveOccurred())
787
788 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentRenamer(deploy))
789
790 By("returning error")
791 Expect(err).To(HaveOccurred())
792
793 By("returning OperationResultNone")
794 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
795 })
796
797 It("errors when object namespace changes", func() {
798 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr)
799
800 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated))
801 Expect(err).NotTo(HaveOccurred())
802
803 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentNamespaceChanger(deploy))
804
805 By("returning error")
806 Expect(err).To(HaveOccurred())
807
808 By("returning OperationResultNone")
809 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
810 })
811
812 It("aborts immediately if there was an error initially retrieving the object", func() {
813 op, err := controllerutil.CreateOrPatch(context.TODO(), errorReader{c}, deploy, func() error {
814 Fail("Mutation method should not run")
815 return nil
816 })
817
818 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone))
819 Expect(err).To(HaveOccurred())
820 })
821 })
822
823 Describe("Finalizers", func() {
824 var deploy *appsv1.Deployment
825
826 Describe("AddFinalizer", func() {
827 deploy = &appsv1.Deployment{
828 ObjectMeta: metav1.ObjectMeta{
829 Finalizers: []string{},
830 },
831 }
832
833 It("should add the finalizer when not present", func() {
834 controllerutil.AddFinalizer(deploy, testFinalizer)
835 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer}))
836 })
837
838 It("should not add the finalizer when already present", func() {
839 controllerutil.AddFinalizer(deploy, testFinalizer)
840 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer}))
841 })
842 })
843
844 Describe("RemoveFinalizer", func() {
845 It("should remove finalizer if present", func() {
846 controllerutil.RemoveFinalizer(deploy, testFinalizer)
847 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{}))
848 })
849
850 It("should remove all equal finalizers if present", func() {
851 deploy.SetFinalizers(append(deploy.Finalizers, testFinalizer, testFinalizer))
852 controllerutil.RemoveFinalizer(deploy, testFinalizer)
853 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{}))
854 })
855 })
856
857 Describe("AddFinalizer, which returns an indication of whether it modified the object's list of finalizers,", func() {
858 deploy = &appsv1.Deployment{
859 ObjectMeta: metav1.ObjectMeta{
860 Finalizers: []string{},
861 },
862 }
863
864 When("the object's list of finalizers has no instances of the input finalizer", func() {
865 It("should return true", func() {
866 Expect(controllerutil.AddFinalizer(deploy, testFinalizer)).To(BeTrue())
867 })
868 It("should add the input finalizer to the object's list of finalizers", func() {
869 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer}))
870 })
871 })
872
873 When("the object's list of finalizers has an instance of the input finalizer", func() {
874 It("should return false", func() {
875 Expect(controllerutil.AddFinalizer(deploy, testFinalizer)).To(BeFalse())
876 })
877 It("should not modify the object's list of finalizers", func() {
878 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer}))
879 })
880 })
881 })
882
883 Describe("RemoveFinalizer, which returns an indication of whether it modified the object's list of finalizers,", func() {
884 When("the object's list of finalizers has no instances of the input finalizer", func() {
885 It("should return false", func() {
886 Expect(controllerutil.RemoveFinalizer(deploy, testFinalizer1)).To(BeFalse())
887 })
888 It("should not modify the object's list of finalizers", func() {
889 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer}))
890 })
891 })
892
893 When("the object's list of finalizers has one instance of the input finalizer", func() {
894 It("should return true", func() {
895 Expect(controllerutil.RemoveFinalizer(deploy, testFinalizer)).To(BeTrue())
896 })
897 It("should remove the instance of the input finalizer from the object's list of finalizers", func() {
898 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{}))
899 })
900 })
901
902 When("the object's list of finalizers has multiple instances of the input finalizer", func() {
903 It("should return true", func() {
904 deploy.SetFinalizers(append(deploy.Finalizers, testFinalizer, testFinalizer))
905 Expect(controllerutil.RemoveFinalizer(deploy, testFinalizer)).To(BeTrue())
906 })
907 It("should remove each instance of the input finalizer from the object's list of finalizers", func() {
908 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{}))
909 })
910 })
911 })
912
913 Describe("ContainsFinalizer", func() {
914 It("should check that finalizer is present", func() {
915 controllerutil.AddFinalizer(deploy, testFinalizer)
916 Expect(controllerutil.ContainsFinalizer(deploy, testFinalizer)).To(BeTrue())
917 })
918
919 It("should check that finalizer is not present after RemoveFinalizer call", func() {
920 controllerutil.RemoveFinalizer(deploy, testFinalizer)
921 Expect(controllerutil.ContainsFinalizer(deploy, testFinalizer)).To(BeFalse())
922 })
923 })
924 })
925 })
926
927 const (
928 testFinalizer = "foo.bar.baz"
929 testFinalizer1 = testFinalizer + "1"
930 )
931
932 var (
933 _ runtime.Object = &errRuntimeObj{}
934 _ metav1.Object = &errMetaObj{}
935 )
936
937 type errRuntimeObj struct {
938 runtime.TypeMeta
939 }
940
941 func (o *errRuntimeObj) DeepCopyObject() runtime.Object {
942 return &errRuntimeObj{}
943 }
944
945 type errMetaObj struct {
946 metav1.ObjectMeta
947 }
948
949 func deploymentSpecr(deploy *appsv1.Deployment, spec appsv1.DeploymentSpec) controllerutil.MutateFn {
950 return func() error {
951 deploy.Spec = spec
952 return nil
953 }
954 }
955
956 func deploymentStatusr(deploy *appsv1.Deployment, status appsv1.DeploymentStatus) controllerutil.MutateFn {
957 return func() error {
958 deploy.Status = status
959 return nil
960 }
961 }
962
963 var deploymentIdentity controllerutil.MutateFn = func() error {
964 return nil
965 }
966
967 func deploymentRenamer(deploy *appsv1.Deployment) controllerutil.MutateFn {
968 return func() error {
969 deploy.Name = fmt.Sprintf("%s-1", deploy.Name)
970 return nil
971 }
972 }
973
974 func deploymentNamespaceChanger(deploy *appsv1.Deployment) controllerutil.MutateFn {
975 return func() error {
976 deploy.Namespace = fmt.Sprintf("%s-1", deploy.Namespace)
977 return nil
978 }
979 }
980
981 func deploymentScaler(deploy *appsv1.Deployment, replicas int32) controllerutil.MutateFn {
982 fn := func() error {
983 deploy.Spec.Replicas = &replicas
984 return nil
985 }
986 return fn
987 }
988
989 type errorReader struct {
990 client.Client
991 }
992
993 func (e errorReader) Get(ctx context.Context, key client.ObjectKey, into client.Object, opts ...client.GetOption) error {
994 return fmt.Errorf("unexpected error")
995 }
996
View as plain text