github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/test/integration/redis_hscale_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  	"strconv"
    22  	"strings"
    23  
    24  	. "github.com/onsi/ginkgo/v2"
    25  	. "github.com/onsi/gomega"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  
    31  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    32  	"github.com/1aal/kubeblocks/pkg/common"
    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("Redis Horizontal Scale function", func() {
    40  
    41  	const clusterDefName = "test-clusterdef"
    42  	const clusterVersionName = "test-clusterversion"
    43  	const clusterNamePrefix = "test-cluster"
    44  
    45  	const scriptConfigName = "test-cluster-redis-scripts"
    46  	const primaryConfigName = "redis-primary-config"
    47  	const secondaryConfigName = "redis-secondary-config"
    48  
    49  	const replicas = 3
    50  
    51  	// Cleanups
    52  
    53  	cleanEnv := func() {
    54  		// must wait until resources deleted and no longer exist 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 configurations
    64  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    65  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    66  		// namespaced
    67  		testapps.ClearResources(&testCtx, intctrlutil.ConfigMapSignature, inNS, ml)
    68  		// non-namespaced
    69  		testapps.ClearResources(&testCtx, intctrlutil.ConfigConstraintSignature, ml)
    70  		testapps.ClearResources(&testCtx, intctrlutil.BackupPolicyTemplateSignature, ml)
    71  
    72  	}
    73  
    74  	BeforeEach(cleanEnv)
    75  
    76  	AfterEach(cleanEnv)
    77  
    78  	// Testcases
    79  
    80  	var (
    81  		clusterDefObj     *appsv1alpha1.ClusterDefinition
    82  		clusterVersionObj *appsv1alpha1.ClusterVersion
    83  		clusterObj        *appsv1alpha1.Cluster
    84  		clusterKey        types.NamespacedName
    85  	)
    86  
    87  	testReplicationRedisHorizontalScale := func() {
    88  
    89  		By("Mock a cluster obj with replication workloadType.")
    90  		pvcSpec := testapps.NewPVCSpec("1Gi")
    91  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix,
    92  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
    93  			AddComponent(testapps.DefaultRedisCompSpecName, testapps.DefaultRedisCompDefName).
    94  			SetReplicas(replicas).AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
    95  			Create(&testCtx).GetObject()
    96  		clusterKey = client.ObjectKeyFromObject(clusterObj)
    97  
    98  		By("Waiting for cluster creation")
    99  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   100  
   101  		By("Waiting for the cluster to be running")
   102  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.RunningClusterPhase))
   103  
   104  		By("Checking statefulSet number")
   105  		stsList := testk8s.ListAndCheckStatefulSet(&testCtx, clusterKey)
   106  		Expect(len(stsList.Items)).Should(BeEquivalentTo(1))
   107  
   108  		By("Checking pods number and role label in StatefulSet")
   109  		podList, err := common.GetPodListByStatefulSet(ctx, k8sClient, &stsList.Items[0])
   110  		Expect(err).To(Succeed())
   111  		Expect(len(podList)).Should(BeEquivalentTo(replicas))
   112  		for _, pod := range podList {
   113  			if strings.HasSuffix(pod.Name, strconv.Itoa(testapps.DefaultReplicationCandidateIndex)) {
   114  				Expect(pod.Labels[constant.RoleLabelKey]).Should(BeEquivalentTo(constant.Primary))
   115  			} else {
   116  				Expect(pod.Labels[constant.RoleLabelKey]).Should(BeEquivalentTo(constant.Secondary))
   117  			}
   118  		}
   119  
   120  		By("Checking services status")
   121  		svcList := &corev1.ServiceList{}
   122  		Expect(k8sClient.List(ctx, svcList, client.MatchingLabels{
   123  			constant.AppInstanceLabelKey: clusterKey.Name,
   124  		}, client.InNamespace(clusterKey.Namespace))).Should(Succeed())
   125  		// we should have both external service and headless service
   126  		Expect(len(svcList.Items)).Should(Equal(2))
   127  		var externalSvc corev1.Service
   128  		for _, svc := range svcList.Items {
   129  			if svc.Spec.ClusterIP != "None" {
   130  				externalSvc = svc
   131  			}
   132  		}
   133  		Expect(externalSvc).ShouldNot(BeNil())
   134  
   135  		for _, newReplicas := range []int32{4, 2, 7, 1} {
   136  			By(fmt.Sprintf("horizontal scale out to %d", newReplicas))
   137  			Expect(testapps.ChangeObj(&testCtx, clusterObj, func(lcluster *appsv1alpha1.Cluster) {
   138  				lcluster.Spec.ComponentSpecs[0].Replicas = newReplicas
   139  			})).Should(Succeed())
   140  
   141  			By("Wait for the cluster to be running")
   142  			Consistently(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.RunningClusterPhase))
   143  
   144  			By("Checking pods' status and count are updated in cluster status after scale-out")
   145  			Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, fetched *appsv1alpha1.Cluster) {
   146  				compName := fetched.Spec.ComponentSpecs[0].Name
   147  				g.Expect(fetched.Status.Components).NotTo(BeNil())
   148  				g.Expect(fetched.Status.Components).To(HaveKey(compName))
   149  				replicationStatus := fetched.Status.Components[compName].ReplicationSetStatus
   150  				g.Expect(replicationStatus).NotTo(BeNil())
   151  				g.Expect(len(replicationStatus.Secondaries)).To(BeEquivalentTo(newReplicas - 1))
   152  			})).Should(Succeed())
   153  		}
   154  	}
   155  
   156  	// Scenarios
   157  
   158  	Context("with Redis defined as replication Type and doing Horizontal scale", func() {
   159  		BeforeEach(func() {
   160  			_ = testapps.CreateCustomizedObj(&testCtx, "resources/redis-scripts.yaml", &corev1.ConfigMap{},
   161  				testapps.WithName(scriptConfigName), testCtx.UseDefaultNamespace())
   162  
   163  			_ = testapps.CreateCustomizedObj(&testCtx, "resources/redis-primary-config-template.yaml", &corev1.ConfigMap{},
   164  				testapps.WithName(primaryConfigName), testCtx.UseDefaultNamespace())
   165  
   166  			_ = testapps.CreateCustomizedObj(&testCtx, "resources/redis-secondary-config-template.yaml", &corev1.ConfigMap{},
   167  				testapps.WithName(secondaryConfigName), testCtx.UseDefaultNamespace())
   168  
   169  			replicationRedisConfigVolumeMounts := []corev1.VolumeMount{
   170  				{
   171  					Name:      constant.Primary,
   172  					MountPath: "/etc/conf/primary",
   173  				},
   174  				{
   175  					Name:      constant.Secondary,
   176  					MountPath: "/etc/conf/secondary",
   177  				},
   178  			}
   179  
   180  			By("Create a clusterDefinition obj with replication workloadType.")
   181  			mode := int32(0755)
   182  			clusterDefObj = testapps.NewClusterDefFactory(clusterDefName).
   183  				AddComponentDef(testapps.ReplicationRedisComponent, testapps.DefaultRedisCompDefName).
   184  				AddScriptTemplate(scriptConfigName, scriptConfigName, testCtx.DefaultNamespace, testapps.ScriptsVolumeName, &mode).
   185  				AddConfigTemplate(primaryConfigName, primaryConfigName, "", testCtx.DefaultNamespace, constant.Primary).
   186  				AddConfigTemplate(secondaryConfigName, secondaryConfigName, "", testCtx.DefaultNamespace, constant.Secondary).
   187  				AddInitContainerVolumeMounts(testapps.DefaultRedisInitContainerName, replicationRedisConfigVolumeMounts).
   188  				AddContainerVolumeMounts(testapps.DefaultRedisContainerName, replicationRedisConfigVolumeMounts).
   189  				Create(&testCtx).GetObject()
   190  
   191  			By("Create a clusterVersion obj with replication workloadType.")
   192  			clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.Name).
   193  				AddComponentVersion(testapps.DefaultRedisCompDefName).
   194  				AddInitContainerShort(testapps.DefaultRedisInitContainerName, testapps.DefaultRedisImageName).
   195  				AddContainerShort(testapps.DefaultRedisContainerName, testapps.DefaultRedisImageName).
   196  				Create(&testCtx).GetObject()
   197  		})
   198  
   199  		It("Should success with one primary and x secondaries when changes the number of replicas", func() {
   200  			testReplicationRedisHorizontalScale()
   201  		})
   202  	})
   203  })