github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/test/integration/mysql_scale_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package appstest 18 19 import ( 20 "fmt" 21 22 . "github.com/onsi/ginkgo/v2" 23 . "github.com/onsi/gomega" 24 25 appsv1 "k8s.io/api/apps/v1" 26 corev1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/api/resource" 29 "k8s.io/apimachinery/pkg/types" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 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 testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s" 37 ) 38 39 var _ = Describe("MySQL Scaling function", func() { 40 const clusterDefName = "test-clusterdef" 41 const clusterVersionName = "test-clusterversion" 42 const clusterNamePrefix = "test-cluster" 43 const scriptConfigName = "test-cluster-mysql-scripts" 44 const mysqlCompDefName = "replicasets" 45 const mysqlCompName = "mysql" 46 47 // Cleanups 48 49 cleanAll := func() { 50 // must wait until resources deleted and no longer exist before the testcases start, 51 // otherwise if later it needs to create some new resource objects with the same name, 52 // in race conditions, it will find the existence of old objects, resulting failure to 53 // create the new objects. 54 By("clean resources") 55 56 // delete cluster(and all dependent sub-resources), clusterversion and clusterdef 57 testapps.ClearClusterResources(&testCtx) 58 59 inNS := client.InNamespace(testCtx.DefaultNamespace) 60 ml := client.HasLabels{testCtx.TestObjLabelKey} 61 // namespaced 62 testapps.ClearResources(&testCtx, intctrlutil.OpsRequestSignature, inNS, ml) 63 testapps.ClearResources(&testCtx, intctrlutil.ConfigMapSignature, inNS, ml) 64 } 65 66 BeforeEach(cleanAll) 67 68 AfterEach(cleanAll) 69 70 // Testcases 71 72 var ( 73 clusterDefObj *appsv1alpha1.ClusterDefinition 74 clusterVersionObj *appsv1alpha1.ClusterVersion 75 clusterObj *appsv1alpha1.Cluster 76 clusterKey types.NamespacedName 77 ) 78 79 testVerticalScaleCPUAndMemory := func() { 80 const opsName = "mysql-verticalscaling" 81 82 By("Create a cluster obj") 83 resources := corev1.ResourceRequirements{ 84 Limits: corev1.ResourceList{ 85 "cpu": resource.MustParse("800m"), 86 "memory": resource.MustParse("512Mi"), 87 }, 88 Requests: corev1.ResourceList{ 89 "cpu": resource.MustParse("500m"), 90 "memory": resource.MustParse("256Mi"), 91 }, 92 } 93 clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix, 94 clusterDefObj.Name, clusterVersionObj.Name).WithRandomName(). 95 AddComponent(mysqlCompName, mysqlCompDefName). 96 SetResources(resources).SetReplicas(1). 97 Create(&testCtx).GetObject() 98 clusterKey = client.ObjectKeyFromObject(clusterObj) 99 Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1)) 100 101 By("check cluster running") 102 Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) { 103 g.Expect(cluster.Status.Phase).To(Equal(appsv1alpha1.RunningClusterPhase)) 104 })).Should(Succeed()) 105 106 By("send VerticalScalingOpsRequest successfully") 107 opsKey := types.NamespacedName{Name: opsName, Namespace: testCtx.DefaultNamespace} 108 verticalScalingOpsRequest := testapps.NewOpsRequestObj(opsKey.Name, opsKey.Namespace, 109 clusterObj.Name, appsv1alpha1.VerticalScalingType) 110 verticalScalingOpsRequest.Spec.TTLSecondsAfterSucceed = 0 111 verticalScalingOpsRequest.Spec.VerticalScalingList = []appsv1alpha1.VerticalScaling{ 112 { 113 ComponentOps: appsv1alpha1.ComponentOps{ComponentName: mysqlCompName}, 114 ResourceRequirements: corev1.ResourceRequirements{ 115 Requests: corev1.ResourceList{ 116 "cpu": resource.MustParse("400m"), 117 "memory": resource.MustParse("300Mi"), 118 }, 119 }, 120 }, 121 } 122 Expect(testCtx.CreateObj(testCtx.Ctx, verticalScalingOpsRequest)).Should(Succeed()) 123 124 By("check VerticalScalingOpsRequest succeed") 125 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(verticalScalingOpsRequest), 126 func(g Gomega, ops *appsv1alpha1.OpsRequest) { 127 g.Expect(ops.Status.Phase == appsv1alpha1.OpsSucceedPhase).To(BeTrue()) 128 })).Should(Succeed()) 129 130 By("check cluster resource requirements changed") 131 Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, fetched *appsv1alpha1.Cluster) { 132 g.Expect(fetched.Spec.ComponentSpecs[0].Resources.Requests).To(Equal( 133 verticalScalingOpsRequest.Spec.VerticalScalingList[0].Requests)) 134 })).Should(Succeed()) 135 136 By("check OpsRequest reclaimed after ttl") 137 Expect(testapps.ChangeObj(&testCtx, verticalScalingOpsRequest, func(lopsReq *appsv1alpha1.OpsRequest) { 138 lopsReq.Spec.TTLSecondsAfterSucceed = 1 139 })).Should(Succeed()) 140 141 By("OpsRequest reclaimed after ttl") 142 Eventually(func() error { 143 return k8sClient.Get(testCtx.Ctx, client.ObjectKeyFromObject(verticalScalingOpsRequest), verticalScalingOpsRequest) 144 }).Should(Satisfy(apierrors.IsNotFound)) 145 } 146 147 testVerticalScaleStorage := func() { 148 oldStorageValue := resource.MustParse("1Gi") 149 newStorageValue := resource.MustParse("2Gi") 150 151 By("Check StorageClass") 152 defaultStorageClass := testk8s.GetDefaultStorageClass(&testCtx) 153 if defaultStorageClass == nil { 154 Skip("No default StorageClass found") 155 } else if !(defaultStorageClass.AllowVolumeExpansion != nil && *defaultStorageClass.AllowVolumeExpansion) { 156 Skip("Default StorageClass doesn't allow resize") 157 } 158 159 By("Create a cluster obj with both log and data volume of 1GB size") 160 dataPvcSpec := testapps.NewPVCSpec(oldStorageValue.String()) 161 logPvcSpec := dataPvcSpec 162 logPvcSpec.StorageClassName = &defaultStorageClass.Name 163 clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix, 164 clusterDefObj.Name, clusterVersionObj.Name).WithRandomName(). 165 AddComponent(mysqlCompName, mysqlCompDefName). 166 AddVolumeClaimTemplate(testapps.DataVolumeName, dataPvcSpec). 167 AddVolumeClaimTemplate(testapps.LogVolumeName, logPvcSpec). 168 Create(&testCtx).GetObject() 169 clusterKey = client.ObjectKeyFromObject(clusterObj) 170 171 Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1)) 172 173 By("Check the replicas") 174 Eventually(func(g Gomega) { 175 stsList := &appsv1.StatefulSetList{} 176 g.Expect(k8sClient.List(testCtx.Ctx, stsList, client.MatchingLabels{ 177 constant.AppInstanceLabelKey: clusterKey.Name, 178 }, client.InNamespace(clusterKey.Namespace))).To(Succeed()) 179 g.Expect(len(stsList.Items) > 0).To(BeTrue()) 180 sts := &stsList.Items[0] 181 g.Expect(sts.Spec.Replicas).NotTo(BeNil()) 182 g.Expect(sts.Status.AvailableReplicas).To(Equal(*sts.Spec.Replicas)) 183 }).Should(Succeed()) 184 185 By("Check the pvc") 186 Eventually(func() bool { 187 pvcList := &corev1.PersistentVolumeClaimList{} 188 Expect(k8sClient.List(testCtx.Ctx, pvcList, client.InNamespace(clusterKey.Namespace))).Should(Succeed()) 189 return len(pvcList.Items) != 0 190 }).Should(BeTrue()) 191 192 By("Update volume size") 193 Eventually(testapps.GetAndChangeObj(&testCtx, clusterKey, func(fetched *appsv1alpha1.Cluster) { 194 comp := &fetched.Spec.ComponentSpecs[0] 195 comp.VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = newStorageValue 196 comp.VolumeClaimTemplates[1].Spec.Resources.Requests[corev1.ResourceStorage] = newStorageValue 197 })).Should(Succeed()) 198 199 Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(2)) 200 201 By("Checking the PVC") 202 stsList := testk8s.ListAndCheckStatefulSet(&testCtx, clusterKey) 203 for _, sts := range stsList.Items { 204 for _, vct := range sts.Spec.VolumeClaimTemplates { 205 for i := *sts.Spec.Replicas - 1; i >= 0; i-- { 206 pvc := &corev1.PersistentVolumeClaim{} 207 pvcKey := types.NamespacedName{ 208 Namespace: clusterKey.Namespace, 209 Name: fmt.Sprintf("%s-%s-%d", vct.Name, sts.Name, i), 210 } 211 Expect(k8sClient.Get(testCtx.Ctx, pvcKey, pvc)).Should(Succeed()) 212 Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(newStorageValue)) 213 } 214 } 215 } 216 } 217 218 // Scenarios 219 220 Context("with MySQL defined as a stateful component", func() { 221 BeforeEach(func() { 222 _ = testapps.CreateCustomizedObj(&testCtx, "resources/mysql-scripts.yaml", &corev1.ConfigMap{}, 223 testapps.WithName(scriptConfigName), testCtx.UseDefaultNamespace()) 224 225 By("Create a clusterDef obj") 226 mode := int32(0755) 227 clusterDefObj = testapps.NewClusterDefFactory(clusterDefName). 228 AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompDefName). 229 AddScriptTemplate(scriptConfigName, scriptConfigName, testCtx.DefaultNamespace, testapps.ScriptsVolumeName, &mode). 230 Create(&testCtx).GetObject() 231 232 By("Create a clusterVersion obj") 233 clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()). 234 AddComponentVersion(mysqlCompDefName).AddContainerShort(testapps.DefaultMySQLContainerName, testapps.ApeCloudMySQLImage). 235 Create(&testCtx).GetObject() 236 }) 237 238 It("should handle VerticalScalingOpsRequest and change Cluster's cpu&memory requirements", func() { 239 testVerticalScaleCPUAndMemory() 240 }) 241 242 It("should handle PVC resize requests if cluster has storage class which enables dynamic-provisioning", func() { 243 testVerticalScaleStorage() 244 }) 245 }) 246 247 Context("with MySQL defined as a consensus component", func() { 248 BeforeEach(func() { 249 By("Create configmap") 250 _ = testapps.CreateCustomizedObj(&testCtx, "resources/mysql-scripts.yaml", &corev1.ConfigMap{}, 251 testapps.WithName(scriptConfigName), testCtx.UseDefaultNamespace()) 252 253 By("Create a clusterDef obj") 254 mode := int32(0755) 255 clusterDefObj = testapps.NewClusterDefFactory(clusterDefName). 256 AddComponentDef(testapps.ConsensusMySQLComponent, mysqlCompDefName). 257 AddScriptTemplate(scriptConfigName, scriptConfigName, testCtx.DefaultNamespace, testapps.ScriptsVolumeName, &mode). 258 Create(&testCtx).GetObject() 259 260 By("Create a clusterVersion obj") 261 clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()). 262 AddComponentVersion(mysqlCompDefName).AddContainerShort(testapps.DefaultMySQLContainerName, testapps.ApeCloudMySQLImage). 263 Create(&testCtx).GetObject() 264 }) 265 266 It("should handle VerticalScalingOpsRequest and change Cluster's cpu&memory requirements", func() { 267 testVerticalScaleCPUAndMemory() 268 }) 269 270 It("should handle PVC resize requests if cluster has storage class which enables dynamic-provisioning", func() { 271 testVerticalScaleStorage() 272 }) 273 }) 274 })