github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/clusterdefinition_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 apps 21 22 import ( 23 . "github.com/onsi/ginkgo/v2" 24 . "github.com/onsi/gomega" 25 26 corev1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/types" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 30 31 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 32 cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core" 33 "github.com/1aal/kubeblocks/pkg/constant" 34 intctrlutil "github.com/1aal/kubeblocks/pkg/generics" 35 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 36 ) 37 38 var _ = Describe("ClusterDefinition Controller", func() { 39 const clusterDefName = "test-clusterdef" 40 const clusterVersionName = "test-clusterversion" 41 42 const statefulCompDefName = "replicasets" 43 44 const configVolumeName = "mysql-config" 45 46 const cmName = "mysql-tree-node-template-8.0" 47 48 cleanEnv := func() { 49 // must wait till resources deleted and no longer existed before the testcases start, 50 // otherwise if later it needs to create some new resource objects with the same name, 51 // in race conditions, it will find the existence of old objects, resulting failure to 52 // create the new objects. 53 By("clean resources") 54 55 inNS := client.InNamespace(testCtx.DefaultNamespace) 56 ml := client.HasLabels{testCtx.TestObjLabelKey} 57 58 // resources should be released in following order 59 // non-namespaced 60 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ClusterVersionSignature, true, ml) 61 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ClusterDefinitionSignature, true, ml) 62 testapps.ClearResources(&testCtx, intctrlutil.ConfigConstraintSignature, ml) 63 64 // namespaced 65 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ConfigMapSignature, true, inNS, ml) 66 } 67 68 BeforeEach(func() { 69 cleanEnv() 70 71 }) 72 73 AfterEach(func() { 74 cleanEnv() 75 }) 76 77 var ( 78 clusterDefObj *appsv1alpha1.ClusterDefinition 79 clusterVersionObj *appsv1alpha1.ClusterVersion 80 ) 81 82 Context("with no ConfigSpec", func() { 83 BeforeEach(func() { 84 By("Create a clusterDefinition obj") 85 clusterDefObj = testapps.NewClusterDefFactory(clusterDefName). 86 AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName). 87 Create(&testCtx).GetObject() 88 89 By("Create a clusterVersion obj") 90 clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()). 91 AddComponentVersion(statefulCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage). 92 Create(&testCtx).GetObject() 93 }) 94 95 It("should update status of clusterVersion at the same time when updating clusterDefinition", func() { 96 By("Check reconciled finalizer and status of ClusterDefinition") 97 var cdGen int64 98 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj), 99 func(g Gomega, cd *appsv1alpha1.ClusterDefinition) { 100 g.Expect(cd.Finalizers).NotTo(BeEmpty()) 101 g.Expect(cd.Status.ObservedGeneration).To(BeEquivalentTo(1)) 102 cdGen = cd.Status.ObservedGeneration 103 })).Should(Succeed()) 104 105 By("Check reconciled finalizer and status of ClusterVersion") 106 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterVersionObj), 107 func(g Gomega, cv *appsv1alpha1.ClusterVersion) { 108 g.Expect(cv.Finalizers).NotTo(BeEmpty()) 109 g.Expect(cv.Status.ObservedGeneration).To(BeEquivalentTo(1)) 110 g.Expect(cv.Status.ClusterDefGeneration).To(Equal(cdGen)) 111 })).Should(Succeed()) 112 113 By("updating clusterDefinition's spec which then update clusterVersion's status") 114 Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj), 115 func(cd *appsv1alpha1.ClusterDefinition) { 116 cd.Spec.ConnectionCredential["root"] = "password" 117 })).Should(Succeed()) 118 119 By("Check ClusterVersion.Status as updated") 120 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterVersionObj), 121 func(g Gomega, cv *appsv1alpha1.ClusterVersion) { 122 g.Expect(cv.Status.Phase).To(Equal(appsv1alpha1.AvailablePhase)) 123 g.Expect(cv.Status.Message).To(Equal("")) 124 g.Expect(cv.Status.ClusterDefGeneration > cdGen).To(BeTrue()) 125 })).Should(Succeed()) 126 127 // TODO: update components to break @validateClusterVersion, and transit ClusterVersion.Status.Phase to UnavailablePhase 128 }) 129 }) 130 131 assureCfgTplConfigMapObj := func() *corev1.ConfigMap { 132 By("Create a configmap and config template obj") 133 cm := testapps.CreateCustomizedObj(&testCtx, "config/config-template.yaml", &corev1.ConfigMap{}, 134 testCtx.UseDefaultNamespace()) 135 136 cfgTpl := testapps.CreateCustomizedObj(&testCtx, "config/config-constraint.yaml", 137 &appsv1alpha1.ConfigConstraint{}) 138 Expect(testapps.ChangeObjStatus(&testCtx, cfgTpl, func() { 139 cfgTpl.Status.Phase = appsv1alpha1.CCAvailablePhase 140 })).Should(Succeed()) 141 return cm 142 } 143 144 Context("with ConfigSpec", func() { 145 BeforeEach(func() { 146 By("Create a clusterDefinition obj") 147 clusterDefObj = testapps.NewClusterDefFactory(clusterDefName). 148 AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName). 149 AddConfigTemplate(cmName, cmName, cmName, testCtx.DefaultNamespace, configVolumeName). 150 Create(&testCtx).GetObject() 151 152 By("Create a clusterVersion obj") 153 clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()). 154 AddComponentVersion(statefulCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage). 155 Create(&testCtx).GetObject() 156 }) 157 158 It("should stop proceeding the status of clusterDefinition if configmap is invalid or doesn't exist", func() { 159 By("check the reconciler won't update Status.ObservedGeneration if configmap doesn't exist.") 160 // should use Consistently here, since cd.Status.ObservedGeneration is initialized to be zero, 161 // we must watch the value for a while to tell it's not changed by the reconciler. 162 Consistently(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj), 163 func(g Gomega, cd *appsv1alpha1.ClusterDefinition) { 164 g.Expect(cd.Status.ObservedGeneration).To(BeEquivalentTo(0)) 165 })).Should(Succeed()) 166 167 assureCfgTplConfigMapObj() 168 169 By("check the reconciler update Status.ObservedGeneration after configmap is created.") 170 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj), 171 func(g Gomega, cd *appsv1alpha1.ClusterDefinition) { 172 g.Expect(cd.Status.ObservedGeneration).To(BeEquivalentTo(1)) 173 174 // check labels and finalizers 175 g.Expect(cd.Finalizers).ShouldNot(BeEmpty()) 176 configCMLabel := cfgcore.GenerateTPLUniqLabelKeyWithConfig(cmName) 177 configConstraintLabel := cfgcore.GenerateConstraintsUniqLabelKeyWithConfig(cmName) 178 g.Expect(cd.Labels[configCMLabel]).Should(BeEquivalentTo(cmName)) 179 g.Expect(cd.Labels[configConstraintLabel]).Should(BeEquivalentTo(cmName)) 180 })).Should(Succeed()) 181 182 By("check the reconciler update configmap.Finalizer after configmap is created.") 183 cmKey := types.NamespacedName{ 184 Namespace: testCtx.DefaultNamespace, 185 Name: cmName, 186 } 187 Eventually(testapps.CheckObj(&testCtx, cmKey, func(g Gomega, cmObj *corev1.ConfigMap) { 188 g.Expect(controllerutil.ContainsFinalizer(cmObj, constant.ConfigurationTemplateFinalizerName)).To(BeTrue()) 189 })).Should(Succeed()) 190 }) 191 }) 192 })