github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/plan/prepare_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 plan
    21  
    22  import (
    23  	"fmt"
    24  	"reflect"
    25  
    26  	. "github.com/onsi/ginkgo/v2"
    27  	. "github.com/onsi/gomega"
    28  
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    35  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    36  	"github.com/1aal/kubeblocks/pkg/controller/component"
    37  	"github.com/1aal/kubeblocks/pkg/controller/configuration"
    38  	"github.com/1aal/kubeblocks/pkg/controller/factory"
    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  const (
    45  	mysqlCompDefName = "replicasets"
    46  	mysqlCompName    = "mysql"
    47  	nginxCompDefName = "nginx"
    48  	nginxCompName    = "nginx"
    49  	redisCompDefName = "replicasets"
    50  	redisCompName    = "redis"
    51  )
    52  
    53  // buildComponentResources generate all necessary sub-resources objects used in component,
    54  // like Secret, ConfigMap, Service, StatefulSet, Deployment, Volume, PodDisruptionBudget etc.
    55  func buildComponentResources(reqCtx intctrlutil.RequestCtx, cli client.Client,
    56  	clusterDef *appsv1alpha1.ClusterDefinition,
    57  	clusterVer *appsv1alpha1.ClusterVersion,
    58  	cluster *appsv1alpha1.Cluster,
    59  	component *component.SynthesizedComponent) ([]client.Object, error) {
    60  	resources := make([]client.Object, 0)
    61  	if cluster.UID == "" {
    62  		cluster.UID = types.UID("test-uid")
    63  	}
    64  	workloadProcessor := func(customSetup func(*corev1.ConfigMap) (client.Object, error)) error {
    65  		envConfig := factory.BuildEnvConfig(cluster, component)
    66  		resources = append(resources, envConfig)
    67  
    68  		workload, err := customSetup(envConfig)
    69  		if err != nil {
    70  			return err
    71  		}
    72  
    73  		defer func() {
    74  			// workload object should be appended last
    75  			resources = append(resources, workload)
    76  		}()
    77  
    78  		var podSpec *corev1.PodSpec
    79  		sts, ok := workload.(*appsv1.StatefulSet)
    80  		if ok {
    81  			podSpec = &sts.Spec.Template.Spec
    82  		} else {
    83  			deploy, ok := workload.(*appsv1.Deployment)
    84  			if ok {
    85  				podSpec = &deploy.Spec.Template.Spec
    86  			}
    87  		}
    88  		if podSpec == nil {
    89  			return nil
    90  		}
    91  
    92  		defer func() {
    93  			for _, cc := range []*[]corev1.Container{
    94  				&podSpec.Containers,
    95  				&podSpec.InitContainers,
    96  			} {
    97  				volumes := podSpec.Volumes
    98  				for _, c := range *cc {
    99  					for _, v := range c.VolumeMounts {
   100  						// if persistence is not found, add emptyDir pod.spec.volumes[]
   101  						volumes, _ = intctrlutil.CreateOrUpdateVolume(volumes, v.Name, func(volumeName string) corev1.Volume {
   102  							return corev1.Volume{
   103  								Name: v.Name,
   104  								VolumeSource: corev1.VolumeSource{
   105  									EmptyDir: &corev1.EmptyDirVolumeSource{},
   106  								},
   107  							}
   108  						}, nil)
   109  					}
   110  				}
   111  				podSpec.Volumes = volumes
   112  			}
   113  		}()
   114  
   115  		// render config template
   116  		return RenderConfigNScriptFiles(
   117  			&intctrlutil.ResourceCtx{
   118  				Context:       reqCtx.Ctx,
   119  				Client:        cli,
   120  				Namespace:     cluster.GetNamespace(),
   121  				ClusterName:   cluster.GetNamespace(),
   122  				ComponentName: component.Name,
   123  			},
   124  			clusterVer, cluster, component, workload, podSpec, nil)
   125  	}
   126  
   127  	// TODO: may add a PDB transform to Create/Update/Delete.
   128  	// if no these handle, the cluster controller will occur an error during reconciling.
   129  	// conditional build PodDisruptionBudget
   130  	if component.MinAvailable != nil {
   131  		pdb := factory.BuildPDB(cluster, component)
   132  		resources = append(resources, pdb)
   133  	} else {
   134  		panic("this shouldn't happen")
   135  	}
   136  
   137  	// REVIEW/TODO:
   138  	// - need higher level abstraction handling
   139  	// - or move this module to part operator controller handling
   140  	switch component.WorkloadType {
   141  	case appsv1alpha1.Stateful, appsv1alpha1.Consensus, appsv1alpha1.Replication:
   142  		if err := workloadProcessor(
   143  			func(envConfig *corev1.ConfigMap) (client.Object, error) {
   144  				return factory.BuildSts(reqCtx, cluster, component, envConfig.Name)
   145  			}); err != nil {
   146  			return nil, err
   147  		}
   148  	}
   149  
   150  	return resources, nil
   151  }
   152  
   153  var _ = Describe("Cluster Controller", func() {
   154  
   155  	cleanEnv := func() {
   156  		// must wait until resources deleted and no longer exist before the testcases start,
   157  		// otherwise if later it needs to create some new resource objects with the same name,
   158  		// in race conditions, it will find the existence of old objects, resulting failure to
   159  		// create the new objects.
   160  		By("clean resources")
   161  
   162  		inNS := client.InNamespace(testCtx.DefaultNamespace)
   163  		ml := client.HasLabels{testCtx.TestObjLabelKey}
   164  
   165  		// non-namespaced
   166  		testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml)
   167  
   168  		// namespaced
   169  		testapps.ClearResources(&testCtx, generics.ConfigMapSignature, inNS, ml)
   170  	}
   171  
   172  	BeforeEach(func() {
   173  		cleanEnv()
   174  	})
   175  
   176  	AfterEach(func() {
   177  		cleanEnv()
   178  	})
   179  
   180  	const (
   181  		clusterDefName     = "test-clusterdef"
   182  		clusterVersionName = "test-clusterversion"
   183  		clusterName        = "test-cluster"
   184  	)
   185  	var (
   186  		clusterDef     *appsv1alpha1.ClusterDefinition
   187  		clusterVersion *appsv1alpha1.ClusterVersion
   188  		cluster        *appsv1alpha1.Cluster
   189  		configSpecName string
   190  	)
   191  
   192  	isStatefulSet := func(v string) bool {
   193  		return v == "StatefulSet"
   194  	}
   195  
   196  	Context("with Deployment workload", func() {
   197  		BeforeEach(func() {
   198  			clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   199  				AddComponentDef(testapps.StatelessNginxComponent, nginxCompDefName).
   200  				GetObject()
   201  			clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   202  				AddComponentVersion(nginxCompDefName).
   203  				AddContainerShort("nginx", testapps.NginxImage).
   204  				GetObject()
   205  			cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   206  				clusterDef.Name, clusterVersion.Name).
   207  				AddComponent(nginxCompDefName, nginxCompName).
   208  				GetObject()
   209  		})
   210  
   211  		It("should construct pdb", func() {
   212  			reqCtx := intctrlutil.RequestCtx{
   213  				Ctx: ctx,
   214  				Log: logger,
   215  			}
   216  			component, err := component.BuildComponent(
   217  				reqCtx,
   218  				nil,
   219  				cluster,
   220  				clusterDef,
   221  				&clusterDef.Spec.ComponentDefs[0],
   222  				&cluster.Spec.ComponentSpecs[0],
   223  				nil,
   224  				&clusterVersion.Spec.ComponentVersions[0])
   225  			Expect(err).Should(Succeed())
   226  
   227  			resources, err := buildComponentResources(reqCtx, testCtx.Cli, clusterDef, clusterVersion, cluster, component)
   228  			Expect(err).Should(Succeed())
   229  
   230  			expects := []string{
   231  				"PodDisruptionBudget",
   232  			}
   233  			Expect(resources).Should(HaveLen(len(expects)))
   234  			for i, v := range expects {
   235  				Expect(reflect.TypeOf(resources[i]).String()).Should(ContainSubstring(v), fmt.Sprintf("failed at idx %d", i))
   236  			}
   237  		})
   238  	})
   239  
   240  	Context("with Stateful workload and without config template", func() {
   241  		BeforeEach(func() {
   242  			clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   243  				AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompDefName).
   244  				GetObject()
   245  			clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   246  				AddComponentVersion(mysqlCompDefName).
   247  				AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   248  				GetObject()
   249  			pvcSpec := testapps.NewPVCSpec("1Gi")
   250  			cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   251  				clusterDef.Name, clusterVersion.Name).
   252  				AddComponent(mysqlCompName, mysqlCompDefName).
   253  				AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   254  				GetObject()
   255  		})
   256  
   257  		It("should construct env, default ClusterIP service, headless service and statefuset objects and should not render config template", func() {
   258  			reqCtx := intctrlutil.RequestCtx{
   259  				Ctx: ctx,
   260  				Log: logger,
   261  			}
   262  			component, err := component.BuildComponent(
   263  				reqCtx,
   264  				nil,
   265  				cluster,
   266  				clusterDef,
   267  				&clusterDef.Spec.ComponentDefs[0],
   268  				&cluster.Spec.ComponentSpecs[0],
   269  				nil,
   270  				&clusterVersion.Spec.ComponentVersions[0],
   271  			)
   272  			Expect(err).Should(Succeed())
   273  
   274  			resources, err := buildComponentResources(reqCtx, testCtx.Cli, clusterDef, clusterVersion, cluster, component)
   275  			Expect(err).Should(Succeed())
   276  
   277  			expects := []string{
   278  				"PodDisruptionBudget",
   279  				"ConfigMap",
   280  				"StatefulSet",
   281  			}
   282  			Expect(resources).Should(HaveLen(len(expects)))
   283  			for i, v := range expects {
   284  				Expect(reflect.TypeOf(resources[i]).String()).Should(ContainSubstring(v), fmt.Sprintf("failed at idx %d", i))
   285  				if isStatefulSet(v) {
   286  					container := clusterDef.Spec.ComponentDefs[0].PodSpec.Containers[0]
   287  					sts := resources[i].(*appsv1.StatefulSet)
   288  					Expect(len(sts.Spec.Template.Spec.Volumes)).Should(Equal(len(container.VolumeMounts)))
   289  				}
   290  			}
   291  		})
   292  	})
   293  
   294  	Context("with Stateful workload and with config template", func() {
   295  		BeforeEach(func() {
   296  			cm := testapps.CreateCustomizedObj(&testCtx, "config/envfrom-config.yaml", &corev1.ConfigMap{},
   297  				testCtx.UseDefaultNamespace())
   298  
   299  			cfgTpl := testapps.CreateCustomizedObj(&testCtx, "config/envfrom-constraint.yaml",
   300  				&appsv1alpha1.ConfigConstraint{})
   301  
   302  			configSpecName = cm.Name
   303  			clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   304  				AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompDefName).
   305  				AddConfigTemplate(cm.Name, cm.Name, cfgTpl.Name, testCtx.DefaultNamespace, "mysql-config", testapps.DefaultMySQLContainerName, "not-exist").
   306  				GetObject()
   307  			clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   308  				AddComponentVersion(mysqlCompDefName).
   309  				AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   310  				GetObject()
   311  			pvcSpec := testapps.NewPVCSpec("1Gi")
   312  			cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   313  				clusterDef.Name, clusterVersion.Name).
   314  				AddComponent(mysqlCompName, mysqlCompDefName).
   315  				AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   316  				GetObject()
   317  		})
   318  
   319  		It("should render config template", func() {
   320  			reqCtx := intctrlutil.RequestCtx{
   321  				Ctx: ctx,
   322  				Log: logger,
   323  			}
   324  			component, err := component.BuildComponent(
   325  				reqCtx,
   326  				nil,
   327  				cluster,
   328  				clusterDef,
   329  				&clusterDef.Spec.ComponentDefs[0],
   330  				&cluster.Spec.ComponentSpecs[0],
   331  				nil,
   332  				&clusterVersion.Spec.ComponentVersions[0])
   333  			Expect(err).Should(Succeed())
   334  
   335  			resources, err := buildComponentResources(reqCtx, testCtx.Cli, clusterDef, clusterVersion, cluster, component)
   336  			Expect(err).Should(Succeed())
   337  
   338  			expects := []string{
   339  				"PodDisruptionBudget",
   340  				"ConfigMap",
   341  				"StatefulSet",
   342  			}
   343  			Expect(resources).Should(HaveLen(len(expects)))
   344  			for i, v := range expects {
   345  				Expect(reflect.TypeOf(resources[i]).String()).Should(ContainSubstring(v), fmt.Sprintf("failed at idx %d", i))
   346  				if isStatefulSet(v) {
   347  					sts := resources[i].(*appsv1.StatefulSet)
   348  					Expect(configuration.CheckEnvFrom(&sts.Spec.Template.Spec.Containers[0], cfgcore.GenerateEnvFromName(cfgcore.GetComponentCfgName(cluster.Name, component.Name, configSpecName)))).Should(BeTrue())
   349  				}
   350  			}
   351  		})
   352  	})
   353  
   354  	Context("with Stateful workload and with config template and with config volume mount", func() {
   355  		BeforeEach(func() {
   356  			cm := testapps.CreateCustomizedObj(&testCtx, "config/config-template.yaml", &corev1.ConfigMap{},
   357  				testCtx.UseDefaultNamespace())
   358  
   359  			cfgTpl := testapps.CreateCustomizedObj(&testCtx, "config/config-constraint.yaml",
   360  				&appsv1alpha1.ConfigConstraint{})
   361  
   362  			clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   363  				AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompDefName).
   364  				AddConfigTemplate(cm.Name, cm.Name, cfgTpl.Name, testCtx.DefaultNamespace, "mysql-config").
   365  				AddContainerVolumeMounts("mysql", []corev1.VolumeMount{{Name: "mysql-config", MountPath: "/mnt/config"}}).
   366  				GetObject()
   367  			clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   368  				AddComponentVersion(mysqlCompDefName).
   369  				AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   370  				GetObject()
   371  			pvcSpec := testapps.NewPVCSpec("1Gi")
   372  			cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   373  				clusterDef.Name, clusterVersion.Name).
   374  				AddComponent(mysqlCompName, mysqlCompDefName).
   375  				AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   376  				GetObject()
   377  		})
   378  
   379  		It("should add config manager sidecar container", func() {
   380  			reqCtx := intctrlutil.RequestCtx{
   381  				Ctx: ctx,
   382  				Log: logger,
   383  			}
   384  			component, err := component.BuildComponent(
   385  				reqCtx,
   386  				nil,
   387  				cluster,
   388  				clusterDef,
   389  				&clusterDef.Spec.ComponentDefs[0],
   390  				&cluster.Spec.ComponentSpecs[0],
   391  				nil,
   392  				&clusterVersion.Spec.ComponentVersions[0])
   393  			Expect(err).Should(Succeed())
   394  
   395  			resources, err := buildComponentResources(reqCtx, testCtx.Cli, clusterDef, clusterVersion, cluster, component)
   396  			Expect(err).Should(Succeed())
   397  
   398  			expects := []string{
   399  				"PodDisruptionBudget",
   400  				"ConfigMap",
   401  				"StatefulSet",
   402  			}
   403  			Expect(resources).Should(HaveLen(len(expects)))
   404  			for i, v := range expects {
   405  				Expect(reflect.TypeOf(resources[i]).String()).Should(ContainSubstring(v), fmt.Sprintf("failed at idx %d", i))
   406  				if isStatefulSet(v) {
   407  					sts := resources[i].(*appsv1.StatefulSet)
   408  					podSpec := sts.Spec.Template.Spec
   409  					Expect(len(podSpec.Containers) >= 3).Should(BeTrue())
   410  				}
   411  			}
   412  			originPodSpec := clusterDef.Spec.ComponentDefs[0].PodSpec
   413  			Expect(len(originPodSpec.Containers)).Should(Equal(1))
   414  		})
   415  	})
   416  
   417  	// for test GetContainerWithVolumeMount
   418  	Context("with Consensus workload and with external service", func() {
   419  		var (
   420  			clusterDef     *appsv1alpha1.ClusterDefinition
   421  			clusterVersion *appsv1alpha1.ClusterVersion
   422  			cluster        *appsv1alpha1.Cluster
   423  		)
   424  
   425  		BeforeEach(func() {
   426  			clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   427  				AddComponentDef(testapps.ConsensusMySQLComponent, mysqlCompDefName).
   428  				AddComponentDef(testapps.StatelessNginxComponent, nginxCompDefName).
   429  				GetObject()
   430  			clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   431  				AddComponentVersion(mysqlCompDefName).
   432  				AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   433  				AddComponentVersion(nginxCompDefName).
   434  				AddContainerShort("nginx", testapps.NginxImage).
   435  				GetObject()
   436  			pvcSpec := testapps.NewPVCSpec("1Gi")
   437  			cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   438  				clusterDef.Name, clusterVersion.Name).
   439  				AddComponent(mysqlCompName, mysqlCompDefName).
   440  				AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   441  				GetObject()
   442  		})
   443  
   444  		It("should construct env, headless service, statefuset and external service objects", func() {
   445  			reqCtx := intctrlutil.RequestCtx{
   446  				Ctx: ctx,
   447  				Log: logger,
   448  			}
   449  			component, err := component.BuildComponent(
   450  				reqCtx,
   451  				nil,
   452  				cluster,
   453  				clusterDef,
   454  				&clusterDef.Spec.ComponentDefs[0],
   455  				&cluster.Spec.ComponentSpecs[0],
   456  				nil,
   457  				&clusterVersion.Spec.ComponentVersions[0])
   458  			Expect(err).Should(Succeed())
   459  			resources, err := buildComponentResources(reqCtx, testCtx.Cli, clusterDef, clusterVersion, cluster, component)
   460  			Expect(err).Should(Succeed())
   461  			expects := []string{
   462  				"PodDisruptionBudget",
   463  				"ConfigMap",
   464  				"StatefulSet",
   465  			}
   466  			Expect(resources).Should(HaveLen(len(expects)))
   467  			for i, v := range expects {
   468  				Expect(reflect.TypeOf(resources[i]).String()).Should(ContainSubstring(v), fmt.Sprintf("failed at idx %d", i))
   469  			}
   470  		})
   471  	})
   472  
   473  	// for test GetContainerWithVolumeMount
   474  	Context("with Replications workload without pvc", func() {
   475  		var (
   476  			clusterDef     *appsv1alpha1.ClusterDefinition
   477  			clusterVersion *appsv1alpha1.ClusterVersion
   478  			cluster        *appsv1alpha1.Cluster
   479  		)
   480  
   481  		BeforeEach(func() {
   482  			clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   483  				AddComponentDef(testapps.ReplicationRedisComponent, redisCompDefName).
   484  				AddComponentDef(testapps.StatelessNginxComponent, nginxCompDefName).
   485  				GetObject()
   486  			clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   487  				AddComponentVersion(redisCompDefName).
   488  				AddContainerShort("redis", testapps.DefaultRedisImageName).
   489  				AddComponentVersion(nginxCompDefName).
   490  				AddContainerShort("nginx", testapps.NginxImage).
   491  				GetObject()
   492  			cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   493  				clusterDef.Name, clusterVersion.Name).
   494  				AddComponent(redisCompName, redisCompDefName).
   495  				SetReplicas(2).
   496  				GetObject()
   497  		})
   498  
   499  		It("should construct env, statefuset object", func() {
   500  			reqCtx := intctrlutil.RequestCtx{
   501  				Ctx: ctx,
   502  				Log: logger,
   503  			}
   504  			component, err := component.BuildComponent(
   505  				reqCtx,
   506  				nil,
   507  				cluster,
   508  				clusterDef,
   509  				&clusterDef.Spec.ComponentDefs[0],
   510  				&cluster.Spec.ComponentSpecs[0],
   511  				nil,
   512  				&clusterVersion.Spec.ComponentVersions[0])
   513  			Expect(err).Should(Succeed())
   514  
   515  			resources, err := buildComponentResources(reqCtx, testCtx.Cli, clusterDef, clusterVersion, cluster, component)
   516  			Expect(err).Should(Succeed())
   517  
   518  			Expect(resources).Should(HaveLen(3))
   519  			Expect(reflect.TypeOf(resources[0]).String()).Should(ContainSubstring("PodDisruptionBudget"))
   520  			Expect(reflect.TypeOf(resources[1]).String()).Should(ContainSubstring("ConfigMap"))
   521  			Expect(reflect.TypeOf(resources[2]).String()).Should(ContainSubstring("StatefulSet"))
   522  		})
   523  	})
   524  
   525  	// TODO: (free6om)
   526  	//  uncomment following test case until pre-provisoned PVC work begin
   527  	// // for test GetContainerWithVolumeMount
   528  	// Context("with Replications workload with pvc", func() {
   529  	// 	var (
   530  	// 		clusterDef     *appsv1alpha1.ClusterDefinition
   531  	// 		clusterVersion *appsv1alpha1.ClusterVersion
   532  	// 		cluster        *appsv1alpha1.Cluster
   533  	// 	)
   534  	//
   535  	// 	BeforeEach(func() {
   536  	// 		clusterDef = testapps.NewClusterDefFactory(clusterDefName).
   537  	// 			AddComponentDef(testapps.ReplicationRedisComponent, redisCompDefName).
   538  	// 			AddComponentDef(testapps.StatelessNginxComponent, nginxCompDefName).
   539  	// 			GetObject()
   540  	// 		clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   541  	// 			AddComponentVersion(redisCompDefName).
   542  	// 			AddContainerShort("redis", testapps.DefaultRedisImageName).
   543  	// 			AddComponentVersion(nginxCompDefName).
   544  	// 			AddContainerShort("nginx", testapps.NginxImage).
   545  	// 			GetObject()
   546  	// 		pvcSpec := testapps.NewPVCSpec("1Gi")
   547  	// 		cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   548  	// 			clusterDef.Name, clusterVersion.Name).
   549  	// 			AddComponentVersion(redisCompName, redisCompDefName).
   550  	// 			SetReplicas(2).
   551  	// 			AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   552  	// 			GetObject()
   553  	// 	})
   554  	//
   555  	// 	It("should construct pvc objects for each replica", func() {
   556  	// 		reqCtx := intctrlutil.RequestCtx{
   557  	// 			Ctx: ctx,
   558  	// 			Log: logger,
   559  	// 		}
   560  	// 		component := component.BuildComponent(
   561  	// 			reqCtx,
   562  	// 			*cluster,
   563  	// 			*clusterDef,
   564  	// 			clusterDef.Spec.ComponentDefs[0],
   565  	// 			cluster.Spec.ComponentSpecs[0],
   566  	// 			&clusterVersion.Spec.ComponentVersions[0])
   567  	// 		task := types.InitReconcileTask(clusterDef, clusterVersion, cluster, component)
   568  	// 		Expect(PrepareComponentResources(reqCtx, testCtx.Cli, task)).Should(Succeed())
   569  	//
   570  	// 		resources := *task.Resources
   571  	// 		Expect(resources).Should(HaveLen(6))
   572  	// 		Expect(reflect.TypeOf(resources[0]).String()).Should(ContainSubstring("ConfigMap"))
   573  	// 		Expect(reflect.TypeOf(resources[1]).String()).Should(ContainSubstring("Service"))
   574  	// 		Expect(reflect.TypeOf(resources[2]).String()).Should(ContainSubstring("PersistentVolumeClaim"))
   575  	// 		Expect(reflect.TypeOf(resources[3]).String()).Should(ContainSubstring("PersistentVolumeClaim"))
   576  	// 		Expect(reflect.TypeOf(resources[4]).String()).Should(ContainSubstring("StatefulSet"))
   577  	// 		Expect(reflect.TypeOf(resources[5]).String()).Should(ContainSubstring("Service"))
   578  	// 	})
   579  	// })
   580  
   581  })