github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controllerutil/controller_common_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package controllerutil 21 22 import ( 23 "context" 24 "errors" 25 "fmt" 26 "reflect" 27 "testing" 28 "time" 29 30 . "github.com/onsi/ginkgo/v2" 31 . "github.com/onsi/gomega" 32 "k8s.io/apimachinery/pkg/types" 33 34 "github.com/sethvargo/go-password/password" 35 corev1 "k8s.io/api/core/v1" 36 apierrors "k8s.io/apimachinery/pkg/api/errors" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/runtime/schema" 39 ctrl "sigs.k8s.io/controller-runtime" 40 "sigs.k8s.io/controller-runtime/pkg/client" 41 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 42 "sigs.k8s.io/controller-runtime/pkg/reconcile" 43 44 "github.com/1aal/kubeblocks/pkg/constant" 45 ) 46 47 var tlog = ctrl.Log.WithName("controller_testing") 48 49 func TestRequeueWithError(t *testing.T) { 50 _, err := CheckedRequeueWithError(errors.New("test error"), tlog, "test") 51 if err == nil { 52 t.Error("Expected error to fall through, got nil") 53 } 54 55 // test with empty msg 56 _, err = CheckedRequeueWithError(errors.New("test error"), tlog, "") 57 if err == nil { 58 t.Error("Expected error to fall through, got nil") 59 } 60 } 61 62 func TestRequeueWithNotFoundError(t *testing.T) { 63 notFoundErr := apierrors.NewNotFound(schema.GroupResource{ 64 Resource: "Pod", 65 }, "no-body") 66 _, err := CheckedRequeueWithError(notFoundErr, tlog, "test") 67 if err != nil { 68 t.Error("Expected error to be nil, got:", err) 69 } 70 } 71 72 func TestRequeueAfter(t *testing.T) { 73 _, err := RequeueAfter(time.Millisecond, tlog, "test") 74 if err != nil { 75 t.Error("Expected error to be nil, got:", err) 76 } 77 78 // test with empty msg 79 _, err = RequeueAfter(time.Millisecond, tlog, "") 80 if err != nil { 81 t.Error("Expected error to be nil, got:", err) 82 } 83 } 84 85 func TestRequeue(t *testing.T) { 86 res, err := Requeue(tlog, "test") 87 if err != nil { 88 t.Error("Expected error to be nil, got:", err) 89 } 90 if !res.Requeue { 91 t.Error("Expected requeue to be true, got false") 92 } 93 94 // test with empty msg 95 res, err = Requeue(tlog, "") 96 if err != nil { 97 t.Error("Expected error to be nil, got:", err) 98 } 99 if !res.Requeue { 100 t.Error("Expected requeue to be true, got false") 101 } 102 } 103 104 func TestReconciled(t *testing.T) { 105 res, err := Reconciled() 106 if err != nil { 107 t.Error("Expected error to be nil, got:", err) 108 } 109 if res.Requeue { 110 t.Error("Expected requeue to be false, got true") 111 } 112 } 113 114 func TestResultToP(t *testing.T) { 115 res, _ := ResultToP(reconcile.Result{}, nil) 116 if reflect.ValueOf(res).Kind() != reflect.Ptr { 117 t.Error("Expect to get a pointer type, got:", reflect.ValueOf(res).Kind()) 118 } 119 } 120 121 var _ = Describe("Cluster Controller", func() { 122 123 const finalizer = "finalizer/protection" 124 const referencedLabelKey = "referenced/name" 125 126 getObj := func(key client.ObjectKey) *corev1.ConfigMap { 127 cm := &corev1.ConfigMap{} 128 Ω(k8sClient.Get(context.Background(), key, cm)).ShouldNot(HaveOccurred()) 129 return cm 130 } 131 132 getObjWithFinalizer := func(key client.ObjectKey) *corev1.ConfigMap { 133 obj := getObj(key) 134 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, nil) 135 Ω(err).ShouldNot(HaveOccurred()) 136 Expect(res).Should(BeNil()) 137 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).Should(BeTrue()) 138 return getObj(key) 139 } 140 141 getDeletingObj := func(key client.ObjectKey) *corev1.ConfigMap { 142 obj := getObj(key) 143 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).Should(BeTrue()) 144 Expect(obj.DeletionTimestamp.IsZero()).ShouldNot(BeTrue()) 145 return obj 146 } 147 148 createObj := func() (*corev1.ConfigMap, client.ObjectKey) { 149 By("By create obj") 150 randomStr, _ := password.Generate(6, 0, 0, true, false) 151 key := client.ObjectKey{ 152 Name: "cm-" + randomStr, 153 Namespace: "default", 154 } 155 obj := &corev1.ConfigMap{ 156 ObjectMeta: metav1.ObjectMeta{ 157 Name: key.Name, 158 Namespace: key.Namespace, 159 }, 160 } 161 Ω(k8sClient.Create(context.Background(), obj)).ShouldNot(HaveOccurred()) 162 return obj, key 163 } 164 165 deleteObj := func(key client.ObjectKey) { 166 Eventually(func() error { 167 cm := &corev1.ConfigMap{} 168 if err := k8sClient.Get(context.Background(), key, cm); err == nil { 169 controllerutil.RemoveFinalizer(cm, finalizer) 170 return k8sClient.Delete(context.Background(), cm) 171 } else { 172 return client.IgnoreNotFound(err) 173 } 174 }).Should(Succeed()) 175 } 176 177 createReferencedConfigMap := func(label string) *corev1.ConfigMap { 178 By("By create obj") 179 randomStr, _ := password.Generate(6, 0, 0, true, false) 180 key := client.ObjectKey{ 181 Name: "cm-" + randomStr, 182 Namespace: "default", 183 } 184 obj := &corev1.ConfigMap{ 185 ObjectMeta: metav1.ObjectMeta{ 186 Name: key.Name, 187 Namespace: key.Namespace, 188 Labels: map[string]string{ 189 referencedLabelKey: label, 190 }, 191 }, 192 } 193 Ω(k8sClient.Create(context.Background(), obj)).ShouldNot(HaveOccurred()) 194 return obj 195 } 196 197 BeforeEach(func() { 198 // Add any steup steps that needs to be executed before each test 199 }) 200 201 AfterEach(func() { 202 // Add any teardown steps that needs to be executed after each test 203 }) 204 205 Context("When calling HandleCRDeletion for non-deleting obj", func() { 206 It("Should have finalizer added and not reconciled", func() { 207 _, key := createObj() 208 By("By calling HandleCRDeletion") 209 _ = getObjWithFinalizer(key) 210 deleteObj(key) 211 }) 212 }) 213 214 Context("When calling HandleCRDeletion for deleting obj", func() { 215 It("Do deletion with no finalizer should simply reconciled", func() { 216 obj, key := createObj() 217 By("By fake delete obj") 218 obj.DeletionTimestamp = &metav1.Time{ 219 Time: time.Now(), 220 } 221 By("By calling HandleCRDeletion") 222 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, func() (*ctrl.Result, error) { 223 return nil, nil 224 }) 225 Ω(err).ShouldNot(HaveOccurred()) 226 Expect(res).ShouldNot(BeNil()) 227 Expect(*res == reconcile.Result{}).Should(BeTrue()) 228 229 deleteObj(key) 230 }) 231 232 It("Do normal deletion with finalizer should have finalizer removed and reconciled", func() { 233 _, key := createObj() 234 By("By delete obj") 235 obj := getObjWithFinalizer(key) 236 Ω(k8sClient.Delete(ctx, obj)).ShouldNot(HaveOccurred()) 237 238 By("By calling HandleCRDeletion") 239 obj = getObj(key) 240 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, func() (*ctrl.Result, error) { 241 return nil, nil 242 }) 243 Ω(err).ShouldNot(HaveOccurred()) 244 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).ShouldNot(BeTrue()) 245 Expect(res).ShouldNot(BeNil()) 246 Expect(*res == reconcile.Result{}).Should(BeTrue()) 247 }) 248 249 It("Do normal deletion with empty handler should have finalizer removed and reconciled", func() { 250 _, key := createObj() 251 By("By delete obj") 252 obj := getObjWithFinalizer(key) 253 Ω(k8sClient.Delete(ctx, obj)).ShouldNot(HaveOccurred()) 254 255 By("By calling HandleCRDeletion") 256 obj = getDeletingObj(key) 257 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, nil) 258 Ω(err).ShouldNot(HaveOccurred()) 259 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).ShouldNot(BeTrue()) 260 Expect(res).ShouldNot(BeNil()) 261 Expect(*res == reconcile.Result{}).Should(BeTrue()) 262 }) 263 264 It("Do normal deletion with error", func() { 265 _, key := createObj() 266 By("By delete obj") 267 obj := getObjWithFinalizer(key) 268 Ω(k8sClient.Delete(ctx, obj)).ShouldNot(HaveOccurred()) 269 270 By("By calling HandleCRDeletion") 271 obj = getDeletingObj(key) 272 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, func() (*ctrl.Result, error) { 273 return nil, fmt.Errorf("err") 274 }) 275 Ω(err).Should(HaveOccurred()) 276 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).Should(BeTrue()) 277 Expect(res).ShouldNot(BeNil()) 278 Expect(*res == reconcile.Result{}).Should(BeTrue()) 279 }) 280 281 It("Do normal deletion with custom result", func() { 282 _, key := createObj() 283 By("By delete obj") 284 obj := getObjWithFinalizer(key) 285 Ω(k8sClient.Delete(ctx, obj)).ShouldNot(HaveOccurred()) 286 287 By("By calling HandleCRDeletion") 288 obj = getDeletingObj(key) 289 cRes := ctrl.Result{ 290 Requeue: true, 291 } 292 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, func() (*ctrl.Result, error) { 293 return &cRes, nil 294 }) 295 Ω(err).ShouldNot(HaveOccurred()) 296 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).Should(BeTrue()) 297 Expect(res).ShouldNot(BeNil()) 298 Expect(*res == cRes).Should(BeTrue()) 299 }) 300 301 It("Do normal deletion with custom result and error", func() { 302 _, key := createObj() 303 By("By delete obj") 304 obj := getObjWithFinalizer(key) 305 Ω(k8sClient.Delete(ctx, obj)).ShouldNot(HaveOccurred()) 306 307 By("By calling HandleCRDeletion") 308 obj = getDeletingObj(key) 309 cRes := ctrl.Result{ 310 Requeue: true, 311 } 312 res, err := HandleCRDeletion(reqCtx, k8sClient, obj, finalizer, func() (*ctrl.Result, error) { 313 return &cRes, fmt.Errorf("err") 314 }) 315 Ω(err).Should(HaveOccurred()) 316 Expect(controllerutil.ContainsFinalizer(obj, finalizer)).Should(BeTrue()) 317 Expect(res).ShouldNot(BeNil()) 318 Expect(*res == cRes).Should(BeTrue()) 319 }) 320 321 It("Do delete CR with existing referencing CR", func() { 322 obj, key := createObj() 323 _ = createReferencedConfigMap(key.Name) 324 recordEvent := func() { 325 // mock record 326 fmt.Println("mock send event ") 327 } 328 _, err := ValidateReferenceCR(reqCtx, k8sClient, obj, referencedLabelKey, recordEvent, &corev1.ConfigMapList{}) 329 Expect(err == nil) 330 }) 331 332 It("Do test functions which depend on k8s env", func() { 333 By("test IgnoreIsAlreadyExists function") 334 obj, _ := createObj() 335 // reset resourceVersion 336 obj.ResourceVersion = "" 337 err := k8sClient.Create(ctx, obj) 338 Expect(err).Should(HaveOccurred()) 339 Expect(IgnoreIsAlreadyExists(err)).Should(BeNil()) 340 341 By("test ManagedByKubeBlocksFilterPredicate function") 342 Expect(ManagedByKubeBlocksFilterPredicate(obj)).Should(BeFalse()) 343 // set managed by KubeBlocks label 344 obj.Labels = map[string]string{ 345 constant.AppManagedByLabelKey: constant.AppName, 346 } 347 Expect(ManagedByKubeBlocksFilterPredicate(obj)).Should(BeTrue()) 348 349 By("test WorkloadFilterPredicate function") 350 Expect(WorkloadFilterPredicate(obj)).Should(BeFalse()) 351 // set component name label 352 obj.Labels[constant.KBAppComponentLabelKey] = "mysql" 353 Expect(WorkloadFilterPredicate(obj)).Should(BeTrue()) 354 355 By("test RecordCreatedEvent function") 356 RecordCreatedEvent(nil, obj) 357 358 By("test RequeueWithErrorAndRecordEvent function") 359 notFoundErr := NewNotFound("pod not found") 360 _, err = RequeueWithErrorAndRecordEvent(obj, nil, notFoundErr, tlog) 361 Expect(IsNotFound(err)).Should(BeTrue()) 362 }) 363 364 It("Do check resources exists and set ownership", func() { 365 By("obj not exists") 366 notExistObj := &corev1.ConfigMap{} 367 exists, err := CheckResourceExists(ctx, k8sClient, types.NamespacedName{Name: "cm1", Namespace: "default"}, notExistObj) 368 Expect(err).ShouldNot(HaveOccurred()) 369 Expect(exists).Should(BeFalse()) 370 371 By("obj exists") 372 obj, key := createObj() 373 exists, err = CheckResourceExists(ctx, k8sClient, key, obj) 374 Expect(err).ShouldNot(HaveOccurred()) 375 Expect(exists).Should(BeTrue()) 376 377 By("set controllerReference") 378 obj1, _ := createObj() 379 Expect(SetOwnership(obj, obj1, k8sClient.Scheme(), "")).Should(Succeed()) 380 Expect(obj1.OwnerReferences[0].Name).Should(Equal(obj.Name)) 381 Expect(*obj1.OwnerReferences[0].Controller).Should(BeTrue()) 382 383 By("set ownerReference") 384 Expect(SetOwnership(obj, obj1, k8sClient.Scheme(), "", true)).Should(Succeed()) 385 Expect(obj1.OwnerReferences[0].Name).Should(Equal(obj.Name)) 386 Expect(obj1.OwnerReferences[0].Controller).Should(BeNil()) 387 }) 388 }) 389 })