github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/reconfigure_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 operations 21 22 import ( 23 "encoding/json" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 "github.com/spf13/cast" 28 29 corev1 "k8s.io/api/core/v1" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/log" 32 33 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 34 opsutil "github.com/1aal/kubeblocks/controllers/apps/operations/util" 35 "github.com/1aal/kubeblocks/pkg/configuration/core" 36 "github.com/1aal/kubeblocks/pkg/constant" 37 "github.com/1aal/kubeblocks/pkg/controller/builder" 38 configutil "github.com/1aal/kubeblocks/pkg/controller/configuration" 39 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 40 "github.com/1aal/kubeblocks/pkg/generics" 41 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 42 ) 43 44 var _ = Describe("Reconfigure OpsRequest", func() { 45 46 var ( 47 randomStr = testCtx.GetRandomStr() 48 clusterDefinitionName = "cluster-definition-for-ops-" + randomStr 49 clusterVersionName = "clusterversion-for-ops-" + randomStr 50 clusterName = "cluster-for-ops-" + randomStr 51 ) 52 53 cleanEnv := func() { 54 // must wait till resources deleted and no longer existed before the testcases start, 55 // otherwise if later it needs to create some new resource objects with the same name, 56 // in race conditions, it will find the existence of old objects, resulting failure to 57 // create the new objects. 58 By("clean resources") 59 60 // delete cluster(and all dependent sub-resources), clusterversion and clusterdef 61 testapps.ClearClusterResources(&testCtx) 62 63 // delete rest resources 64 inNS := client.InNamespace(testCtx.DefaultNamespace) 65 ml := client.HasLabels{testCtx.TestObjLabelKey} 66 // namespaced 67 testapps.ClearResources(&testCtx, generics.OpsRequestSignature, inNS, ml) 68 testapps.ClearResources(&testCtx, generics.ConfigMapSignature, inNS, ml) 69 // non-namespaced 70 testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml) 71 } 72 73 BeforeEach(cleanEnv) 74 75 AfterEach(cleanEnv) 76 77 initClusterForOps := func(opsRes *OpsResource) { 78 Expect(opsutil.PatchClusterOpsAnnotations(ctx, k8sClient, opsRes.Cluster, nil)).Should(Succeed()) 79 opsRes.Cluster.Status.Phase = appsv1alpha1.RunningClusterPhase 80 } 81 82 assureCfgTplObj := func(tplName, cmName, ns string) (*corev1.ConfigMap, *appsv1alpha1.ConfigConstraint) { 83 By("Assuring an cm obj") 84 cfgCM := testapps.NewCustomizedObj("operations_config/config-template.yaml", 85 &corev1.ConfigMap{}, testapps.WithNamespacedName(cmName, ns)) 86 cfgTpl := testapps.NewCustomizedObj("operations_config/config-constraint.yaml", 87 &appsv1alpha1.ConfigConstraint{}, testapps.WithNamespacedName(tplName, ns)) 88 Expect(testCtx.CheckedCreateObj(ctx, cfgCM)).Should(Succeed()) 89 Expect(testCtx.CheckedCreateObj(ctx, cfgTpl)).Should(Succeed()) 90 91 return cfgCM, cfgTpl 92 } 93 94 assureConfigInstanceObj := func(clusterName, componentName, ns string, cdComponent *appsv1alpha1.ClusterComponentDefinition) (*appsv1alpha1.Configuration, *corev1.ConfigMap) { 95 if len(cdComponent.ConfigSpecs) == 0 { 96 return nil, nil 97 } 98 99 By("create configuration cr") 100 configuration := builder.NewConfigurationBuilder(testCtx.DefaultNamespace, core.GenerateComponentConfigurationName(clusterName, componentName)). 101 ClusterRef(clusterName). 102 Component(componentName) 103 for _, configSpec := range cdComponent.ConfigSpecs { 104 configuration.AddConfigurationItem(configSpec) 105 } 106 Expect(testCtx.CheckedCreateObj(ctx, configuration.GetObject())).Should(Succeed()) 107 108 // update status 109 By("update configuration status") 110 revision := "1" 111 Eventually(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configuration.GetObject()), 112 func(config *appsv1alpha1.Configuration) { 113 revision = cast.ToString(config.GetGeneration()) 114 for _, item := range config.Spec.ConfigItemDetails { 115 configutil.CheckAndUpdateItemStatus(config, item, revision) 116 } 117 })).Should(Succeed()) 118 119 By("create configmap for configSpecs") 120 var cmObj *corev1.ConfigMap 121 for _, configSpec := range cdComponent.ConfigSpecs { 122 cmInsName := core.GetComponentCfgName(clusterName, componentName, configSpec.Name) 123 By("create configmap: " + cmInsName) 124 cfgCM := testapps.NewCustomizedObj("operations_config/config-template.yaml", 125 &corev1.ConfigMap{}, 126 testapps.WithNamespacedName(cmInsName, ns), 127 testapps.WithLabels( 128 constant.AppNameLabelKey, clusterName, 129 constant.ConfigurationRevision, revision, 130 constant.AppInstanceLabelKey, clusterName, 131 constant.KBAppComponentLabelKey, componentName, 132 constant.CMConfigurationTemplateNameLabelKey, configSpec.TemplateRef, 133 constant.CMConfigurationConstraintsNameLabelKey, configSpec.ConfigConstraintRef, 134 constant.CMConfigurationSpecProviderLabelKey, configSpec.Name, 135 constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType, 136 ), 137 ) 138 Expect(testCtx.CheckedCreateObj(ctx, cfgCM)).Should(Succeed()) 139 cmObj = cfgCM 140 } 141 return configuration.GetObject(), cmObj 142 } 143 144 assureMockReconfigureData := func(policyName string) (*OpsResource, *appsv1alpha1.Configuration, *corev1.ConfigMap) { 145 By("init operations resources ") 146 opsRes, clusterDef, clusterObject := initOperationsResources(clusterDefinitionName, clusterVersionName, clusterName) 147 148 var ( 149 cfgObj *corev1.ConfigMap 150 config *appsv1alpha1.Configuration 151 stsComponent *appsv1alpha1.ClusterComponentDefinition 152 ) 153 By("Test Reconfigure") 154 { 155 // mock cluster is Running to support reconfiguring ops 156 By("mock cluster status") 157 patch := client.MergeFrom(clusterObject.DeepCopy()) 158 clusterObject.Status.Phase = appsv1alpha1.RunningClusterPhase 159 Expect(k8sClient.Status().Patch(ctx, clusterObject, patch)).Should(Succeed()) 160 } 161 162 { 163 By("mock config tpl") 164 cmObj, tplObj := assureCfgTplObj("mysql-tpl-test", "mysql-cm-test", testCtx.DefaultNamespace) 165 By("update clusterdefinition tpl") 166 patch := client.MergeFrom(clusterDef.DeepCopy()) 167 for i := range clusterDef.Spec.ComponentDefs { 168 component := &clusterDef.Spec.ComponentDefs[i] 169 if component.Name != consensusComp { 170 continue 171 } 172 stsComponent = component 173 component.ConfigSpecs = []appsv1alpha1.ComponentConfigSpec{{ 174 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 175 Name: "mysql-test", 176 TemplateRef: cmObj.Name, 177 VolumeName: "mysql-config", 178 Namespace: testCtx.DefaultNamespace, 179 }, 180 ConfigConstraintRef: tplObj.Name, 181 }} 182 } 183 184 Expect(k8sClient.Patch(ctx, clusterDef, patch)).Should(Succeed()) 185 By("mock config cm object") 186 config, cfgObj = assureConfigInstanceObj(clusterName, consensusComp, testCtx.DefaultNamespace, stsComponent) 187 } 188 189 return opsRes, config, cfgObj 190 } 191 192 Context("Test Reconfigure", func() { 193 It("Test Reconfigure OpsRequest with restart", func() { 194 opsRes, configuration, _ := assureMockReconfigureData("simple") 195 reqCtx := intctrlutil.RequestCtx{ 196 Ctx: testCtx.Ctx, 197 Log: log.FromContext(ctx).WithName("Reconfigure"), 198 Recorder: opsRes.Recorder, 199 } 200 201 By("mock reconfigure success") 202 ops := testapps.NewOpsRequestObj("reconfigure-ops-"+randomStr, testCtx.DefaultNamespace, 203 clusterName, appsv1alpha1.ReconfiguringType) 204 ops.Spec.Reconfigure = &appsv1alpha1.Reconfigure{ 205 Configurations: []appsv1alpha1.ConfigurationItem{{ 206 Name: "mysql-test", 207 Keys: []appsv1alpha1.ParameterConfig{{ 208 Key: "my.cnf", 209 Parameters: []appsv1alpha1.ParameterPair{ 210 { 211 Key: "binlog_stmt_cache_size", 212 Value: func() *string { v := "4096"; return &v }(), 213 }, 214 { 215 Key: "key", 216 Value: func() *string { v := "abcd"; return &v }(), 217 }, 218 }, 219 }}, 220 }}, 221 ComponentOps: appsv1alpha1.ComponentOps{ComponentName: consensusComp}, 222 } 223 224 By("Init Reconfiguring opsrequest") 225 opsRes.OpsRequest = ops 226 Expect(testCtx.CheckedCreateObj(ctx, ops)).Should(Succeed()) 227 initClusterForOps(opsRes) 228 229 opsManager := GetOpsManager() 230 By("init ops phase") 231 _, err := opsManager.Do(reqCtx, k8sClient, opsRes) 232 Expect(err).ShouldNot(HaveOccurred()) 233 234 By("Reconfigure configure") 235 _, err = opsManager.Do(reqCtx, k8sClient, opsRes) 236 Expect(err).ShouldNot(HaveOccurred()) 237 238 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), opsRes.OpsRequest)).Should(Succeed()) 239 _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) 240 Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(appsv1alpha1.OpsRunningPhase)) 241 242 By("mock configuration.status.phase to Finished") 243 var item *appsv1alpha1.ConfigurationItemDetail 244 Eventually(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configuration), 245 func(config *appsv1alpha1.Configuration) { 246 item = config.Spec.GetConfigurationItem("mysql-test") 247 for i := 0; i < len(config.Status.ConfigurationItemStatus); i++ { 248 config.Status.ConfigurationItemStatus[i].Phase = appsv1alpha1.CFinishedPhase 249 if config.Status.ConfigurationItemStatus[i].Name == item.Name { 250 config.Status.ConfigurationItemStatus[i].ReconcileDetail = &appsv1alpha1.ReconcileDetail{ 251 Policy: "simple", 252 CurrentRevision: config.Status.ConfigurationItemStatus[i].UpdateRevision, 253 SucceedCount: 2, 254 ExpectedCount: 2, 255 } 256 } 257 } 258 })).Should(Succeed()) 259 260 By("mock configmap controller to updated") 261 Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKey{ 262 Name: core.GetComponentCfgName(clusterName, consensusComp, "mysql-test"), 263 Namespace: testCtx.DefaultNamespace}, 264 func(cm *corev1.ConfigMap) { 265 b, err := json.Marshal(item) 266 Expect(err).ShouldNot(HaveOccurred()) 267 if cm.Annotations == nil { 268 cm.Annotations = make(map[string]string) 269 } 270 cm.Annotations[constant.ConfigAppliedVersionAnnotationKey] = string(b) 271 b, err = json.Marshal(intctrlutil.Result{ 272 Phase: appsv1alpha1.CFinishedPhase, 273 Policy: "simple", 274 ExecResult: "none", 275 }) 276 Expect(err).ShouldNot(HaveOccurred()) 277 cm.Annotations[core.GenerateRevisionPhaseKey("1")] = string(b) 278 })).Should(Succeed()) 279 280 By("Reconfigure operation success") 281 // Expect(reAction.Handle(eventContext, ops.Name, appsv1alpha1.OpsSucceedPhase, nil)).Should(Succeed()) 282 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), opsRes.OpsRequest)).Should(Succeed()) 283 _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) 284 Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(appsv1alpha1.OpsSucceedPhase)) 285 286 }) 287 288 It("Test Reconfigure OpsRequest with autoReload", func() { 289 opsRes, _, _ := assureMockReconfigureData("autoReload") 290 reqCtx := intctrlutil.RequestCtx{ 291 Ctx: testCtx.Ctx, 292 Log: log.FromContext(ctx).WithName("Reconfigure"), 293 Recorder: opsRes.Recorder, 294 } 295 296 By("mock reconfigure success") 297 ops := testapps.NewOpsRequestObj("reconfigure-ops-"+randomStr+"-reload", testCtx.DefaultNamespace, 298 clusterName, appsv1alpha1.ReconfiguringType) 299 ops.Spec.Reconfigure = &appsv1alpha1.Reconfigure{ 300 Configurations: []appsv1alpha1.ConfigurationItem{{ 301 Name: "mysql-test", 302 Keys: []appsv1alpha1.ParameterConfig{{ 303 Key: "my.cnf", 304 Parameters: []appsv1alpha1.ParameterPair{ 305 { 306 Key: "binlog_stmt_cache_size", 307 Value: func() *string { v := "4096"; return &v }(), 308 }}, 309 }}, 310 }}, 311 ComponentOps: appsv1alpha1.ComponentOps{ComponentName: consensusComp}, 312 } 313 314 By("Init Reconfiguring opsrequest") 315 opsRes.OpsRequest = ops 316 Expect(testCtx.CheckedCreateObj(ctx, ops)).Should(Succeed()) 317 initClusterForOps(opsRes) 318 319 opsManager := GetOpsManager() 320 // reAction := reconfigureAction{} 321 By("Reconfigure configure") 322 _, err := opsManager.Do(reqCtx, k8sClient, opsRes) 323 Expect(err).ShouldNot(HaveOccurred()) 324 Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase)) 325 // do reconfigure 326 _, err = opsManager.Do(reqCtx, k8sClient, opsRes) 327 Expect(err).ShouldNot(HaveOccurred()) 328 By("configuration Reconcile callback") 329 330 // Expect(reAction.Handle(eventContext, ops.Name, appsv1alpha1.OpsSucceedPhase, nil)).Should(Succeed()) 331 By("Reconfigure configure") 332 _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) 333 // mock cluster.status.component.phase to Updating 334 mockClusterCompPhase := func(clusterObj *appsv1alpha1.Cluster, phase appsv1alpha1.ClusterComponentPhase) { 335 clusterObject := clusterObj.DeepCopy() 336 patch := client.MergeFrom(clusterObject.DeepCopy()) 337 compStatus := clusterObject.Status.Components[consensusComp] 338 compStatus.Phase = phase 339 clusterObject.Status.Components[consensusComp] = compStatus 340 Expect(k8sClient.Status().Patch(ctx, clusterObject, patch)).Should(Succeed()) 341 } 342 mockClusterCompPhase(opsRes.Cluster, appsv1alpha1.UpdatingClusterCompPhase) 343 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.Cluster), opsRes.Cluster)).Should(Succeed()) 344 345 By("check cluster.status.components[*].phase == Reconfiguring") 346 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), opsRes.OpsRequest)).Should(Succeed()) 347 Expect(opsRes.Cluster.Status.Components[consensusComp].Phase).Should(Equal(appsv1alpha1.UpdatingClusterCompPhase)) // appsv1alpha1.ReconfiguringPhase 348 // TODO: add status condition expect 349 _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) 350 // mock cluster.status.component.phase to Running 351 mockClusterCompPhase(opsRes.Cluster, appsv1alpha1.RunningClusterCompPhase) 352 353 By("check cluster.status.components[*].phase == Running") 354 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.Cluster), opsRes.Cluster)).Should(Succeed()) 355 Expect(opsRes.Cluster.Status.Components[consensusComp].Phase).Should(Equal(appsv1alpha1.RunningClusterCompPhase)) 356 }) 357 358 }) 359 })