github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/extensions/addon_controller_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 extensions 21 22 import ( 23 "context" 24 "fmt" 25 "time" 26 27 . "github.com/onsi/ginkgo/v2" 28 . "github.com/onsi/gomega" 29 30 batchv1 "k8s.io/api/batch/v1" 31 corev1 "k8s.io/api/core/v1" 32 apierrors "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/types" 35 "k8s.io/client-go/kubernetes/scheme" 36 "k8s.io/client-go/tools/record" 37 ctrl "sigs.k8s.io/controller-runtime" 38 "sigs.k8s.io/controller-runtime/pkg/client" 39 "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 41 extensionsv1alpha1 "github.com/1aal/kubeblocks/apis/extensions/v1alpha1" 42 "github.com/1aal/kubeblocks/pkg/constant" 43 "github.com/1aal/kubeblocks/pkg/generics" 44 "github.com/1aal/kubeblocks/pkg/testutil" 45 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 46 viper "github.com/1aal/kubeblocks/pkg/viperx" 47 ) 48 49 var _ = Describe("Addon controller", func() { 50 cleanEnv := func() { 51 // must wait till resources deleted and no longer existed before the testcases start, 52 // otherwise if later it needs to create some new resource objects with the same name, 53 // in race conditions, it will find the existence of old objects, resulting failure to 54 // create the new objects. 55 By("clean resources") 56 // non-namespaced 57 ml := client.HasLabels{testCtx.TestObjLabelKey} 58 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.AddonSignature, true, ml) 59 60 inNS := client.InNamespace(viper.GetString(constant.CfgKeyCtrlrMgrNS)) 61 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.JobSignature, true, inNS, 62 client.HasLabels{ 63 constant.AddonNameLabelKey, 64 }) 65 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.JobSignature, true, inNS, 66 client.HasLabels{ 67 constant.AppManagedByLabelKey, 68 }) 69 70 // delete rest mocked objects 71 testapps.ClearResources(&testCtx, generics.ConfigMapSignature, inNS, ml) 72 testapps.ClearResources(&testCtx, generics.SecretSignature, inNS, ml) 73 74 // By("deleting the Namespace to perform the tests") 75 // Eventually(func(g Gomega) { 76 // namespace := testCtx.GetNamespaceObj() 77 // err := testCtx.Cli.Delete(testCtx.Ctx, &namespace) 78 // g.Expect(client.IgnoreNotFound(err)).To(Not(HaveOccurred())) 79 // g.Expect(client.IgnoreNotFound(testCtx.Cli.Get( 80 // testCtx.Ctx, testCtx.GetNamespaceKey(), &namespace))).To(Not(HaveOccurred())) 81 // }).Should(Succeed()) 82 } 83 84 BeforeEach(func() { 85 cleanEnv() 86 }) 87 88 AfterEach(func() { 89 cleanEnv() 90 }) 91 92 Context("Addon controller test", func() { 93 var addon *extensionsv1alpha1.Addon 94 var key types.NamespacedName 95 BeforeEach(func() { 96 cleanEnv() 97 const distro = "kubeblocks" 98 testutil.SetKubeServerVersionWithDistro("1", "24", "0", distro) 99 Expect(client.IgnoreAlreadyExists(testCtx.CreateNamespace())).To(Not(HaveOccurred())) 100 }) 101 102 AfterEach(func() { 103 cleanEnv() 104 viper.Set(constant.CfgKeyCtrlrMgrTolerations, "") 105 viper.Set(constant.CfgKeyCtrlrMgrAffinity, "") 106 viper.Set(constant.CfgKeyCtrlrMgrNodeSelector, "") 107 }) 108 109 doReconcile := func() (ctrl.Result, error) { 110 addonReconciler := &AddonReconciler{ 111 Client: testCtx.Cli, 112 Scheme: testCtx.Cli.Scheme(), 113 } 114 req := reconcile.Request{ 115 NamespacedName: key, 116 } 117 return addonReconciler.Reconcile(ctx, req) 118 } 119 120 doReconcileOnce := func(g Gomega) { 121 By("Reconciling once") 122 result, err := doReconcile() 123 Expect(err).To(Not(HaveOccurred())) 124 Expect(result.Requeue).Should(BeFalse()) 125 } 126 127 getJob := func(g Gomega, jobKey client.ObjectKey) *batchv1.Job { 128 job := &batchv1.Job{} 129 g.Eventually(func(g Gomega) { 130 _, err := doReconcile() 131 g.Expect(err).To(Not(HaveOccurred())) 132 g.Expect(testCtx.Cli.Get(ctx, jobKey, job)).Should(Succeed()) 133 }).Should(Succeed()) 134 return job 135 } 136 137 fakeCompletedJob := func(g Gomega, jobKey client.ObjectKey) { 138 job := getJob(g, jobKey) 139 job.Status.Succeeded = 1 140 job.Status.Active = 0 141 job.Status.Failed = 0 142 g.Expect(testCtx.Cli.Status().Update(ctx, job)).Should(Succeed()) 143 } 144 145 fakeFailedJob := func(g Gomega, jobKey client.ObjectKey) { 146 job := getJob(g, jobKey) 147 job.Status.Failed = 1 148 job.Status.Active = 0 149 job.Status.Succeeded = 0 150 g.Expect(testCtx.Cli.Status().Update(ctx, job)).Should(Succeed()) 151 } 152 153 fakeActiveJob := func(g Gomega, jobKey client.ObjectKey) { 154 job := getJob(g, jobKey) 155 job.Status.Active = 1 156 job.Status.Succeeded = 0 157 job.Status.Failed = 0 158 g.Expect(testCtx.Cli.Status().Update(ctx, job)).Should(Succeed()) 159 } 160 161 checkedJobDeletion := func(g Gomega, jobKey client.ObjectKey) { 162 job := &batchv1.Job{} 163 err := testCtx.Cli.Get(ctx, jobKey, job) 164 if err == nil { 165 g.Expect(job.DeletionTimestamp.IsZero()).Should(BeFalse()) 166 return 167 } 168 g.Expect(apierrors.IsNotFound(err)).Should(BeTrue()) 169 } 170 171 fakeInstallationCompletedJob := func(expectedObservedGeneration int) { 172 jobKey := client.ObjectKey{ 173 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 174 Name: getInstallJobName(addon), 175 } 176 Eventually(func(g Gomega) { 177 fakeCompletedJob(g, jobKey) 178 }).Should(Succeed()) 179 Eventually(func(g Gomega) { 180 _, err := doReconcile() 181 g.Expect(err).To(Not(HaveOccurred())) 182 addon = &extensionsv1alpha1.Addon{} 183 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 184 g.Expect(addon.Status.Phase).Should(Equal(extensionsv1alpha1.AddonEnabled)) 185 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(expectedObservedGeneration)) 186 checkedJobDeletion(g, jobKey) 187 }).Should(Succeed()) 188 } 189 190 fakeInstallationFailedJob := func(expectedObservedGeneration int) { 191 jobKey := client.ObjectKey{ 192 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 193 Name: getInstallJobName(addon), 194 } 195 Eventually(func(g Gomega) { 196 fakeFailedJob(g, jobKey) 197 }).Should(Succeed()) 198 Eventually(func(g Gomega) { 199 _, err := doReconcile() 200 g.Expect(err).To(Not(HaveOccurred())) 201 addon = &extensionsv1alpha1.Addon{} 202 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 203 g.Expect(addon.Status.Phase).Should(Equal(extensionsv1alpha1.AddonFailed)) 204 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(expectedObservedGeneration)) 205 }).Should(Succeed()) 206 } 207 208 fakeUninstallationFailedJob := func(expectedObservedGeneration int) { 209 jobKey := client.ObjectKey{ 210 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 211 Name: getUninstallJobName(addon), 212 } 213 Eventually(func(g Gomega) { 214 fakeFailedJob(g, jobKey) 215 }).Should(Succeed()) 216 Eventually(func(g Gomega) { 217 result, err := doReconcile() 218 g.Expect(err).To(Not(HaveOccurred())) 219 g.Expect(result.Requeue).To(BeTrue()) 220 }).Should(Succeed()) 221 Eventually(func(g Gomega) { 222 checkedJobDeletion(g, jobKey) 223 }).Should(Succeed()) 224 } 225 226 createAddonSpecWithRequiredAttributes := func(modifiers func(newOjb *extensionsv1alpha1.Addon)) { 227 if modifiers != nil { 228 addon = testapps.CreateCustomizedObj(&testCtx, "addon/addon.yaml", 229 &extensionsv1alpha1.Addon{}, modifiers) 230 } else { 231 addon = testapps.CreateCustomizedObj(&testCtx, "addon/addon.yaml", 232 &extensionsv1alpha1.Addon{}) 233 } 234 key = types.NamespacedName{ 235 Name: addon.Name, 236 } 237 Expect(addon.Spec.DefaultInstallValues).ShouldNot(BeEmpty()) 238 } 239 240 addonStatusPhaseCheck := func(genID int, expectPhase extensionsv1alpha1.AddonPhase, handler func()) { 241 Eventually(func(g Gomega) { 242 _, err := doReconcile() 243 Expect(err).To(Not(HaveOccurred())) 244 addon = &extensionsv1alpha1.Addon{} 245 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 246 g.Expect(addon.Generation).Should(BeEquivalentTo(genID)) 247 g.Expect(addon.Spec.InstallSpec).ShouldNot(BeNil()) 248 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(genID)) 249 g.Expect(addon.Status.Phase).Should(Equal(expectPhase)) 250 }).Should(Succeed()) 251 252 if handler == nil { 253 return 254 } 255 256 handler() 257 Eventually(func(g Gomega) { 258 _, err := doReconcile() 259 Expect(err).To(Not(HaveOccurred())) 260 addon = &extensionsv1alpha1.Addon{} 261 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 262 g.Expect(addon.Generation).Should(BeEquivalentTo(genID)) 263 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(genID)) 264 g.Expect(addon.Status.Phase).Should(Equal(expectPhase)) 265 }).Should(Succeed()) 266 } 267 268 enablingPhaseCheck := func(genID int) { 269 addonStatusPhaseCheck(genID, extensionsv1alpha1.AddonEnabling, func() { 270 By("By fake active install job") 271 jobKey := client.ObjectKey{ 272 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 273 Name: getInstallJobName(addon), 274 } 275 Eventually(func(g Gomega) { 276 fakeActiveJob(g, jobKey) 277 }).Should(Succeed()) 278 }) 279 } 280 281 disablingPhaseCheck := func(genID int) { 282 addonStatusPhaseCheck(genID, extensionsv1alpha1.AddonDisabling, nil) 283 } 284 285 checkAddonDeleted := func(g Gomega) { 286 addon = &extensionsv1alpha1.Addon{} 287 err := testCtx.Cli.Get(ctx, key, addon) 288 g.Expect(err).To(HaveOccurred()) 289 g.Expect(apierrors.IsNotFound(err)).Should(BeTrue()) 290 } 291 292 disableAddon := func(genID int) { 293 addon = &extensionsv1alpha1.Addon{} 294 Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 295 addon.Spec.InstallSpec.Enabled = false 296 Expect(testCtx.Cli.Update(ctx, addon)).Should(Succeed()) 297 disablingPhaseCheck(genID) 298 } 299 300 fakeHelmRelease := func() { 301 // create fake helm release 302 helmRelease := &corev1.Secret{ 303 ObjectMeta: metav1.ObjectMeta{ 304 Name: fmt.Sprintf("sh.helm.release.v1.%s.v1", addon.Name), 305 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 306 Labels: map[string]string{ 307 "owner": "helm", 308 "name": getHelmReleaseName(addon), 309 }, 310 }, 311 Type: "helm.sh/release.v1", 312 } 313 Expect(testCtx.CreateObj(ctx, helmRelease)).Should(Succeed()) 314 } 315 316 It("should successfully reconcile a custom resource for Addon with spec.type=Helm", func() { 317 By("By create an addon") 318 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 319 newOjb.Spec.Type = extensionsv1alpha1.HelmType 320 newOjb.Spec.Helm = &extensionsv1alpha1.HelmTypeInstallSpec{ 321 InstallOptions: extensionsv1alpha1.HelmInstallOptions{ 322 "--debug": "true", 323 }, 324 ChartLocationURL: "file:///test-charts.tgz", 325 ChartsImage: "kubeblocks-charts", 326 } 327 }) 328 329 By("By checking status.observedGeneration and status.phase=disabled") 330 Eventually(func(g Gomega) { 331 doReconcileOnce(g) 332 addon = &extensionsv1alpha1.Addon{} 333 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 334 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(1)) 335 g.Expect(addon.Status.Phase).Should(Equal(extensionsv1alpha1.AddonDisabled)) 336 }).Should(Succeed()) 337 338 By("By enabling addon with default install") 339 defaultInstall := addon.Spec.DefaultInstallValues[0].AddonInstallSpec 340 addon.Spec.InstallSpec = defaultInstall.DeepCopy() 341 addon.Spec.InstallSpec.Enabled = true 342 Expect(testCtx.Cli.Update(ctx, addon)).Should(Succeed()) 343 enablingPhaseCheck(2) 344 345 By("By enabled addon with fake completed installation job status") 346 fakeInstallationCompletedJob(2) 347 348 By("By checking init container") 349 jobKey := client.ObjectKey{ 350 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 351 Name: getInstallJobName(addon), 352 } 353 Eventually(func(g Gomega) { 354 fakeActiveJob(g, jobKey) 355 }).Should(Succeed()) 356 Eventually(func(g Gomega) { 357 job := getJob(g, jobKey) 358 g.Expect(job.Spec.Template.Spec.InitContainers).Should(HaveLen(1)) 359 }).Should(Succeed()) 360 361 By("By disabling enabled addon") 362 // create fake helm release 363 fakeHelmRelease() 364 disableAddon(3) 365 366 By("By disabled an enabled addon with fake completed uninstall job status") 367 uninstallJobKey := client.ObjectKey{ 368 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 369 Name: getUninstallJobName(addon), 370 } 371 Eventually(func(g Gomega) { 372 fakeCompletedJob(g, uninstallJobKey) 373 }).Should(Succeed()) 374 375 Eventually(func(g Gomega) { 376 _, err := doReconcile() 377 g.Expect(err).To(Not(HaveOccurred())) 378 addon = &extensionsv1alpha1.Addon{} 379 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 380 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(3)) 381 g.Expect(addon.Status.Phase).Should(Equal(extensionsv1alpha1.AddonDisabled)) 382 checkedJobDeletion(g, uninstallJobKey) 383 }).Should(Succeed()) 384 385 By("By delete addon with disabled status") 386 Expect(testCtx.Cli.Delete(ctx, addon)).To(Not(HaveOccurred())) 387 Eventually(func(g Gomega) { 388 _, err := doReconcile() 389 g.Expect(err).To(Not(HaveOccurred())) 390 }).Should(Succeed()) 391 }) 392 393 createAutoInstallAddon := func() { 394 By("By create an addon with auto-install") 395 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 396 newOjb.Spec.Installable.AutoInstall = true 397 }) 398 399 By("By addon autoInstall auto added") 400 enablingPhaseCheck(2) 401 } 402 403 It("should successfully reconcile a custom resource for Addon with autoInstall=true", func() { 404 createAutoInstallAddon() 405 406 By("By enable addon with fake completed install job status") 407 fakeInstallationCompletedJob(2) 408 409 By("By delete addon with enabled status") 410 Expect(testCtx.Cli.Delete(ctx, addon)).To(Not(HaveOccurred())) 411 Eventually(func(g Gomega) { 412 _, err := doReconcile() 413 g.Expect(err).To(Not(HaveOccurred())) 414 checkAddonDeleted(g) 415 }).Should(Succeed()) 416 }) 417 418 It("should successfully reconcile a custom resource for Addon with autoInstall=true with failed uninstall job", func() { 419 createAutoInstallAddon() 420 421 By("By enable addon with fake completed install job status") 422 fakeInstallationCompletedJob(2) 423 424 By("By disabling enabled addon") 425 fakeHelmRelease() 426 disableAddon(3) 427 428 By("By failed an uninstallation job") 429 fakeUninstallationFailedJob(3) 430 431 By("By delete addon with disabling status") 432 Expect(testCtx.Cli.Delete(ctx, addon)).To(Not(HaveOccurred())) 433 Eventually(func(g Gomega) { 434 _, err := doReconcile() 435 g.Expect(err).To(Not(HaveOccurred())) 436 checkAddonDeleted(g) 437 }).Should(Succeed()) 438 }) 439 440 It("should successfully reconcile a custom resource for Addon deletion while enabling", func() { 441 By("By create an addon with auto-install") 442 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 443 newOjb.Spec.Installable.AutoInstall = true 444 }) 445 446 By("By addon autoInstall auto added") 447 enablingPhaseCheck(2) 448 449 By("By delete addon with enabling status") 450 Expect(testCtx.Cli.Delete(ctx, addon)).To(Not(HaveOccurred())) 451 jobKey := client.ObjectKey{ 452 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 453 Name: getUninstallJobName(addon), 454 } 455 Eventually(func(g Gomega) { 456 _, err := doReconcile() 457 g.Expect(err).To(Not(HaveOccurred())) 458 checkAddonDeleted(g) 459 checkedJobDeletion(g, jobKey) 460 }).Should(Succeed()) 461 }) 462 463 It("should successfully reconcile a custom resource for Addon deletion while disabling", func() { 464 createAutoInstallAddon() 465 466 By("By enable addon with fake completed install job status") 467 fakeInstallationCompletedJob(2) 468 469 By("By disabling addon") 470 disableAddon(3) 471 472 By("By delete addon with disabling status") 473 Expect(testCtx.Cli.Delete(ctx, addon)).To(Not(HaveOccurred())) 474 jobKey := client.ObjectKey{ 475 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 476 Name: getUninstallJobName(addon), 477 } 478 Eventually(func(g Gomega) { 479 _, err := doReconcile() 480 g.Expect(err).To(Not(HaveOccurred())) 481 checkAddonDeleted(g) 482 checkedJobDeletion(g, jobKey) 483 }).Should(Succeed()) 484 }) 485 486 It("should successfully reconcile a custom resource for Addon with autoInstall=true with status.phase=Failed", func() { 487 createAutoInstallAddon() 488 489 By("By enabled addon with fake failed install job status") 490 fakeInstallationFailedJob(2) 491 492 By("By disabling addon with failed status") 493 disableAddon(3) 494 495 By("By delete addon with failed status") 496 Expect(testCtx.Cli.Delete(ctx, addon)).To(Not(HaveOccurred())) 497 jobKey := client.ObjectKey{ 498 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 499 Name: getUninstallJobName(addon), 500 } 501 Eventually(func(g Gomega) { 502 _, err := doReconcile() 503 g.Expect(err).To(Not(HaveOccurred())) 504 checkAddonDeleted(g) 505 checkedJobDeletion(g, jobKey) 506 }).Should(Succeed()) 507 }) 508 509 It("should successfully reconcile a custom resource for Addon run job with controller manager schedule settings", func() { 510 viper.Set(constant.CfgKeyCtrlrMgrAffinity, 511 "{\"nodeAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"preference\":{\"matchExpressions\":[{\"key\":\"kb-controller\",\"operator\":\"In\",\"values\":[\"true\"]}]},\"weight\":100}]}}") 512 viper.Set(constant.CfgKeyCtrlrMgrTolerations, 513 "[{\"key\":\"key1\", \"operator\": \"Exists\", \"effect\": \"NoSchedule\"}]") 514 viper.Set(constant.CfgKeyCtrlrMgrNodeSelector, "{\"beta.kubernetes.io/arch\":\"amd64\"}") 515 516 By("By create an addon with auto-install") 517 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 518 newOjb.Spec.Installable.AutoInstall = true 519 }) 520 521 By("By addon autoInstall auto added") 522 enablingPhaseCheck(2) 523 524 By("By checking status.observedGeneration and status.phase=disabled") 525 jobKey := client.ObjectKey{ 526 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 527 Name: getInstallJobName(addon), 528 } 529 Eventually(func(g Gomega) { 530 _, err := doReconcile() 531 g.Expect(err).To(Not(HaveOccurred())) 532 job := &batchv1.Job{} 533 g.Eventually(testCtx.Cli.Get(ctx, jobKey, job)).Should(Succeed()) 534 g.Expect(job.Spec.Template.Spec.Tolerations).ShouldNot(BeEmpty()) 535 g.Expect(job.Spec.Template.Spec.NodeSelector).ShouldNot(BeEmpty()) 536 g.Expect(job.Spec.Template.Spec.Affinity).ShouldNot(BeNil()) 537 g.Expect(job.Spec.Template.Spec.Affinity.NodeAffinity).ShouldNot(BeNil()) 538 }).Should(Succeed()) 539 }) 540 541 It("should successfully reconcile a custom resource for Addon with no matching installable selector", func() { 542 By("By create an addon with no matching installable selector") 543 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 544 newOjb.Spec.Installable.Selectors[0].Values = []string{"some-others"} 545 }) 546 547 By("By checking status.observedGeneration and status.phase=disabled") 548 Eventually(func(g Gomega) { 549 doReconcileOnce(g) 550 addon = &extensionsv1alpha1.Addon{} 551 g.Expect(testCtx.Cli.Get(ctx, key, addon)).To(Not(HaveOccurred())) 552 g.Expect(addon.Status.Phase).Should(Equal(extensionsv1alpha1.AddonDisabled)) 553 g.Expect(addon.Status.Conditions).ShouldNot(BeEmpty()) 554 g.Expect(addon.Status.ObservedGeneration).Should(BeEquivalentTo(1)) 555 }).Should(Succeed()) 556 557 By("By addon with failed installable check") 558 // "extensions.kubeblocks.io/skip-installable-check" 559 }) 560 561 It("should successfully reconcile a custom resource for Addon with CM and secret ref values", func() { 562 By("By create an addon with spec.helm.installValues.configMapRefs set") 563 cm := testapps.CreateCustomizedObj(&testCtx, "addon/cm-values.yaml", 564 &corev1.ConfigMap{}, func(newCM *corev1.ConfigMap) { 565 newCM.Namespace = viper.GetString(constant.CfgKeyCtrlrMgrNS) 566 }) 567 secret := testapps.CreateCustomizedObj(&testCtx, "addon/secret-values.yaml", 568 &corev1.Secret{}, func(newSecret *corev1.Secret) { 569 newSecret.Namespace = viper.GetString(constant.CfgKeyCtrlrMgrNS) 570 }) 571 572 By("By addon enabled via auto-install") 573 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 574 newOjb.Spec.Installable.AutoInstall = true 575 for k := range cm.Data { 576 newOjb.Spec.Helm.InstallValues.ConfigMapRefs = append(newOjb.Spec.Helm.InstallValues.ConfigMapRefs, 577 extensionsv1alpha1.DataObjectKeySelector{ 578 Name: cm.Name, 579 Key: k, 580 }) 581 } 582 for k := range secret.Data { 583 newOjb.Spec.Helm.InstallValues.SecretRefs = append(newOjb.Spec.Helm.InstallValues.SecretRefs, 584 extensionsv1alpha1.DataObjectKeySelector{ 585 Name: secret.Name, 586 Key: k, 587 }) 588 } 589 }) 590 enablingPhaseCheck(2) 591 592 By("By enabled addon with fake completed install job status") 593 fakeInstallationCompletedJob(2) 594 }) 595 596 It("should failed reconcile a custom resource for Addon with missing CM ref values", func() { 597 By("By create an addon with spec.helm.installValues.configMapRefs set") 598 cm := testapps.CreateCustomizedObj(&testCtx, "addon/cm-values.yaml", 599 &corev1.ConfigMap{}, func(newCM *corev1.ConfigMap) { 600 newCM.Namespace = viper.GetString(constant.CfgKeyCtrlrMgrNS) 601 }) 602 603 By("By addon enabled via auto-install") 604 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 605 newOjb.Spec.Installable.AutoInstall = true 606 newOjb.Spec.Helm.InstallValues.ConfigMapRefs = append(newOjb.Spec.Helm.InstallValues.ConfigMapRefs, 607 extensionsv1alpha1.DataObjectKeySelector{ 608 Name: cm.Name, 609 Key: "unknown", 610 }) 611 }) 612 addonStatusPhaseCheck(2, extensionsv1alpha1.AddonFailed, nil) 613 }) 614 615 It("should failed reconcile a custom resource for Addon with missing secret ref values", func() { 616 By("By create an addon with spec.helm.installValues.configMapRefs set") 617 secret := testapps.CreateCustomizedObj(&testCtx, "addon/secret-values.yaml", 618 &corev1.Secret{}, func(newSecret *corev1.Secret) { 619 newSecret.Namespace = viper.GetString(constant.CfgKeyCtrlrMgrNS) 620 }) 621 By("By addon enabled via auto-install") 622 createAddonSpecWithRequiredAttributes(func(newOjb *extensionsv1alpha1.Addon) { 623 newOjb.Spec.Installable.AutoInstall = true 624 newOjb.Spec.Helm.InstallValues.SecretRefs = append(newOjb.Spec.Helm.InstallValues.SecretRefs, 625 extensionsv1alpha1.DataObjectKeySelector{ 626 Name: secret.Name, 627 Key: "unknown", 628 }) 629 }) 630 addonStatusPhaseCheck(2, extensionsv1alpha1.AddonFailed, nil) 631 }) 632 }) 633 634 Context("Addon controller SetupWithManager", func() { 635 It("Do controller SetupWithManager init. flow", func() { 636 By("check SetupWithManager") 637 k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ 638 Scheme: scheme.Scheme, 639 MetricsBindAddress: "0", 640 }) 641 Expect(err).ToNot(HaveOccurred()) 642 reconciler := &AddonReconciler{ 643 Client: k8sManager.GetClient(), 644 Scheme: k8sManager.GetScheme(), 645 } 646 err = reconciler.SetupWithManager(k8sManager) 647 Expect(err).ToNot(HaveOccurred()) 648 649 testEventRecorder := func(recorder record.EventRecorder) { 650 reconciler.Recorder = recorder 651 addon := &extensionsv1alpha1.Addon{} 652 reconciler.Event(addon, corev1.EventTypeNormal, "reason", "message") 653 reconciler.Eventf(addon, corev1.EventTypeNormal, "reason", "%s", "message") 654 reconciler.AnnotatedEventf(addon, map[string]string{ 655 "key": "value", 656 }, corev1.EventTypeNormal, "reason", "message") 657 } 658 659 By("test nil event recorder") 660 testEventRecorder(nil) 661 662 By("test event recorder") 663 testEventRecorder(k8sManager.GetEventRecorderFor("addon-controller")) 664 665 By("start manager") 666 go func() { 667 defer GinkgoRecover() 668 timeoutCtx, cancelFunc := context.WithTimeout(ctx, time.Second) 669 Expect(k8sManager.Start(timeoutCtx)).ToNot(HaveOccurred(), "failed to run manager") 670 if cancelFunc != nil { 671 cancelFunc() 672 } 673 }() 674 675 createJob := func(keys ...string) *batchv1.Job { 676 job := &batchv1.Job{ 677 ObjectMeta: metav1.ObjectMeta{ 678 GenerateName: "addon-test-job", 679 Namespace: testCtx.DefaultNamespace, 680 Labels: map[string]string{}, 681 }, 682 Spec: batchv1.JobSpec{ 683 Template: corev1.PodTemplateSpec{ 684 Spec: corev1.PodSpec{ 685 RestartPolicy: corev1.RestartPolicyNever, 686 Containers: []corev1.Container{ 687 { 688 Name: "kubeblocks", 689 Image: "busybox", 690 }, 691 }, 692 }, 693 }, 694 }, 695 } 696 for _, key := range keys { 697 job.ObjectMeta.Labels[key] = constant.AppName 698 } 699 return job 700 } 701 702 By("create watch obj type without required labels") 703 job := createJob() 704 Expect(k8sClient.Create(ctx, job)).ToNot(HaveOccurred()) 705 706 By("create watch obj with label=" + constant.AddonNameLabelKey) 707 job = createJob(constant.AddonNameLabelKey) 708 Expect(k8sClient.Create(ctx, job)).ToNot(HaveOccurred()) 709 710 By("create watch obj with label=" + constant.AppManagedByLabelKey) 711 job = createJob(constant.AppManagedByLabelKey) 712 Expect(k8sClient.Create(ctx, job)).ToNot(HaveOccurred()) 713 714 By("create watch obj with required labels") 715 job = createJob(constant.AddonNameLabelKey, constant.AppManagedByLabelKey) 716 Expect(k8sClient.Create(ctx, job)).ToNot(HaveOccurred()) 717 }) 718 }) 719 })