github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/create_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 cluster
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"os"
    27  	"reflect"
    28  	"strings"
    29  	"time"
    30  
    31  	. "github.com/onsi/ginkgo/v2"
    32  	. "github.com/onsi/gomega"
    33  	corev1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/api/resource"
    35  	"k8s.io/apimachinery/pkg/util/json"
    36  	"k8s.io/cli-runtime/pkg/genericiooptions"
    37  
    38  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    39  	"github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    40  	"github.com/1aal/kubeblocks/pkg/class"
    41  	"github.com/1aal/kubeblocks/pkg/cli/cluster"
    42  	"github.com/1aal/kubeblocks/pkg/cli/testing"
    43  	"github.com/1aal/kubeblocks/pkg/cli/types"
    44  	"github.com/1aal/kubeblocks/pkg/cli/util"
    45  	"github.com/1aal/kubeblocks/pkg/dataprotection/utils/boolptr"
    46  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    47  )
    48  
    49  func generateComponents(component appsv1alpha1.ClusterComponentSpec, count int) []map[string]interface{} {
    50  	var componentVals []map[string]interface{}
    51  	byteVal, err := json.Marshal(component)
    52  	Expect(err).ShouldNot(HaveOccurred())
    53  	for i := 0; i < count; i++ {
    54  		var componentVal map[string]interface{}
    55  		err = json.Unmarshal(byteVal, &componentVal)
    56  		Expect(err).ShouldNot(HaveOccurred())
    57  		componentVals = append(componentVals, componentVal)
    58  	}
    59  	Expect(len(componentVals)).To(Equal(count))
    60  	return componentVals
    61  }
    62  
    63  func getResource(res corev1.ResourceRequirements, name corev1.ResourceName) interface{} {
    64  	return res.Requests[name].ToUnstructured()
    65  }
    66  
    67  var _ = Describe("create", func() {
    68  	var clsMgr = &class.Manager{}
    69  
    70  	Context("setMonitor", func() {
    71  		var components []map[string]interface{}
    72  		BeforeEach(func() {
    73  			var component appsv1alpha1.ClusterComponentSpec
    74  			component.Monitor = true
    75  			components = generateComponents(component, 3)
    76  		})
    77  
    78  		It("set monitoring interval to 0 to disable monitor", func() {
    79  			setMonitor(0, components)
    80  			for _, c := range components {
    81  				Expect(c[monitorKey]).ShouldNot(BeTrue())
    82  			}
    83  		})
    84  
    85  		It("set monitoring interval to 15 to enable monitor", func() {
    86  			setMonitor(15, components)
    87  			for _, c := range components {
    88  				Expect(c[monitorKey]).Should(BeTrue())
    89  			}
    90  		})
    91  	})
    92  
    93  	Context("setEnableAllLogs Test", func() {
    94  		var cluster *appsv1alpha1.Cluster
    95  		var clusterDef *appsv1alpha1.ClusterDefinition
    96  		BeforeEach(func() {
    97  			cluster = testing.FakeCluster("log", "test")
    98  			clusterDef = testing.FakeClusterDef()
    99  			Expect(cluster.Spec.ComponentSpecs[0].EnabledLogs).Should(BeNil())
   100  		})
   101  		It("no logConfigs in ClusterDef", func() {
   102  			setEnableAllLogs(cluster, clusterDef)
   103  			Expect(len(cluster.Spec.ComponentSpecs[0].EnabledLogs)).Should(Equal(0))
   104  		})
   105  		It("set logConfigs in ClusterDef", func() {
   106  			clusterDef.Spec.ComponentDefs[0].LogConfigs = []appsv1alpha1.LogConfig{
   107  				{
   108  					Name:            "error",
   109  					FilePathPattern: "/log/mysql/mysqld.err",
   110  				},
   111  				{
   112  					Name:            "slow",
   113  					FilePathPattern: "/log/mysql/*slow.log",
   114  				},
   115  			}
   116  			setEnableAllLogs(cluster, clusterDef)
   117  			Expect(cluster.Spec.ComponentSpecs[0].EnabledLogs).Should(Equal([]string{"error", "slow"}))
   118  		})
   119  	})
   120  
   121  	Context("multipleSourceComponent test", func() {
   122  		defer GinkgoRecover()
   123  		streams := genericiooptions.IOStreams{
   124  			In:     os.Stdin,
   125  			Out:    os.Stdout,
   126  			ErrOut: os.Stdout,
   127  		}
   128  		It("target file stored in website", func() {
   129  			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   130  				w.Header().Set("Content-Type", "application/json")
   131  				_, err := w.Write([]byte("OK"))
   132  				Expect(err).ShouldNot(HaveOccurred())
   133  			}))
   134  			defer ts.Close()
   135  			fileURL := ts.URL + "/docs/file"
   136  			bytes, err := MultipleSourceComponents(fileURL, streams.In)
   137  			Expect(bytes).Should(Equal([]byte("OK")))
   138  			Expect(err).ShouldNot(HaveOccurred())
   139  		})
   140  		It("target file doesn't exist", func() {
   141  			fileName := "no-existing-file"
   142  			bytes, err := MultipleSourceComponents(fileName, streams.In)
   143  			Expect(bytes).Should(BeNil())
   144  			Expect(err).Should(HaveOccurred())
   145  		})
   146  	})
   147  
   148  	checkComponent := func(comps []*appsv1alpha1.ClusterComponentSpec, storage string, replicas int32, cpu string, memory string, storageClassName string, compIndex int) {
   149  		Expect(comps).ShouldNot(BeNil())
   150  		Expect(len(comps)).Should(BeNumerically(">=", compIndex))
   151  
   152  		comp := comps[compIndex]
   153  		Expect(getResource(comp.VolumeClaimTemplates[0].Spec.Resources, corev1.ResourceStorage)).Should(Equal(storage))
   154  		Expect(comp.Replicas).Should(BeEquivalentTo(replicas))
   155  
   156  		resources := comp.Resources
   157  		Expect(resources).ShouldNot(BeNil())
   158  		Expect(getResource(resources, corev1.ResourceCPU)).Should(Equal(cpu))
   159  		Expect(getResource(resources, corev1.ResourceMemory)).Should(Equal(memory))
   160  
   161  		if storageClassName == "" {
   162  			Expect(comp.VolumeClaimTemplates[0].Spec.StorageClassName).Should(BeNil())
   163  		} else {
   164  			Expect(*comp.VolumeClaimTemplates[0].Spec.StorageClassName).Should(Equal(storageClassName))
   165  		}
   166  
   167  	}
   168  
   169  	It("build default cluster component without environment", func() {
   170  		dynamic := testing.FakeDynamicClient(testing.FakeClusterDef())
   171  		cd, _ := cluster.GetClusterDefByName(dynamic, testing.ClusterDefName)
   172  		comps, err := buildClusterComp(cd, nil, clsMgr)
   173  		Expect(err).ShouldNot(HaveOccurred())
   174  		checkComponent(comps, "20Gi", 1, "1", "1Gi", "", 0)
   175  	})
   176  
   177  	It("build default cluster component with environment", func() {
   178  		viper.Set(types.CfgKeyClusterDefaultStorageSize, "5Gi")
   179  		viper.Set(types.CfgKeyClusterDefaultReplicas, 1)
   180  		viper.Set(types.CfgKeyClusterDefaultCPU, "2000m")
   181  		viper.Set(types.CfgKeyClusterDefaultMemory, "2Gi")
   182  		dynamic := testing.FakeDynamicClient(testing.FakeClusterDef())
   183  		cd, _ := cluster.GetClusterDefByName(dynamic, testing.ClusterDefName)
   184  		comps, err := buildClusterComp(cd, nil, clsMgr)
   185  		Expect(err).ShouldNot(HaveOccurred())
   186  		checkComponent(comps, "5Gi", 1, "2", "2Gi", "", 0)
   187  	})
   188  
   189  	It("build cluster component with set values", func() {
   190  		dynamic := testing.FakeDynamicClient(testing.FakeClusterDef())
   191  		cd, _ := cluster.GetClusterDefByName(dynamic, testing.ClusterDefName)
   192  		setsMap := map[string]map[setKey]string{
   193  			testing.ComponentDefName: {
   194  				keyCPU:          "10",
   195  				keyMemory:       "2Gi",
   196  				keyStorage:      "10Gi",
   197  				keyReplicas:     "10",
   198  				keyStorageClass: "test",
   199  			},
   200  		}
   201  		comps, err := buildClusterComp(cd, setsMap, clsMgr)
   202  		Expect(err).Should(Succeed())
   203  		checkComponent(comps, "10Gi", 10, "10", "2Gi", "test", 0)
   204  
   205  		setsMap[testing.ComponentDefName][keySwitchPolicy] = "invalid"
   206  		cd.Spec.ComponentDefs[0].WorkloadType = appsv1alpha1.Replication
   207  		_, err = buildClusterComp(cd, setsMap, clsMgr)
   208  		Expect(err).Should(HaveOccurred())
   209  	})
   210  
   211  	It("build multiple cluster component with set values", func() {
   212  		dynamic := testing.FakeDynamicClient(testing.FakeClusterDef())
   213  		cd, _ := cluster.GetClusterDefByName(dynamic, testing.ClusterDefName)
   214  		setsMap := map[string]map[setKey]string{
   215  			testing.ComponentDefName: {
   216  				keyCPU:          "10",
   217  				keyMemory:       "2Gi",
   218  				keyStorage:      "10Gi",
   219  				keyReplicas:     "10",
   220  				keyStorageClass: "test",
   221  			}, testing.ExtraComponentDefName: {
   222  				keyCPU:          "5",
   223  				keyMemory:       "1Gi",
   224  				keyStorage:      "5Gi",
   225  				keyReplicas:     "5",
   226  				keyStorageClass: "test-other",
   227  			},
   228  		}
   229  		comps, err := buildClusterComp(cd, setsMap, clsMgr)
   230  		Expect(err).Should(Succeed())
   231  		checkComponent(comps, "10Gi", 10, "10", "2Gi", "test", 0)
   232  		checkComponent(comps, "5Gi", 5, "5", "1Gi", "test-other", 1)
   233  		setsMap[testing.ComponentDefName][keySwitchPolicy] = "invalid"
   234  		cd.Spec.ComponentDefs[0].WorkloadType = appsv1alpha1.Replication
   235  		_, err = buildClusterComp(cd, setsMap, clsMgr)
   236  		Expect(err).Should(HaveOccurred())
   237  	})
   238  
   239  	mockCD := func(compDefNames []string) *appsv1alpha1.ClusterDefinition {
   240  		cd := &appsv1alpha1.ClusterDefinition{}
   241  		var comps []appsv1alpha1.ClusterComponentDefinition
   242  		for _, n := range compDefNames {
   243  			comp := appsv1alpha1.ClusterComponentDefinition{
   244  				Name:         n,
   245  				WorkloadType: appsv1alpha1.Replication,
   246  			}
   247  			comps = append(comps, comp)
   248  		}
   249  		cd.Spec.ComponentDefs = comps
   250  		return cd
   251  	}
   252  	It("build component and set values map", func() {
   253  		testCases := []struct {
   254  			values       []string
   255  			compDefNames []string
   256  			expected     map[string]map[setKey]string
   257  			success      bool
   258  		}{
   259  			{
   260  				nil,
   261  				nil,
   262  				map[string]map[setKey]string{},
   263  				true,
   264  			},
   265  			{
   266  				[]string{"cpu=1"},
   267  				[]string{"my-comp"},
   268  				map[string]map[setKey]string{
   269  					"my-comp": {
   270  						keyCPU: "1",
   271  					},
   272  				},
   273  				true,
   274  			},
   275  			{
   276  				[]string{"cpu=1,memory=2Gi,storage=10Gi,class=general-1c2g"},
   277  				[]string{"my-comp"},
   278  				map[string]map[setKey]string{
   279  					"my-comp": {
   280  						keyCPU:     "1",
   281  						keyMemory:  "2Gi",
   282  						keyStorage: "10Gi",
   283  						keyClass:   "general-1c2g",
   284  					},
   285  				},
   286  				true,
   287  			},
   288  			// values with unknown set key that will be ignored
   289  			{
   290  				[]string{"cpu=1,memory=2Gi,storage=10Gi,t1,t1=v1"},
   291  				[]string{"my-comp"},
   292  				map[string]map[setKey]string{
   293  					"my-comp": {
   294  						keyCPU:     "1",
   295  						keyMemory:  "2Gi",
   296  						keyStorage: "10Gi",
   297  					},
   298  				},
   299  				false,
   300  			},
   301  			// values with type
   302  			{
   303  				[]string{"type=comp,cpu=1,memory=2Gi,storage=10Gi,t1,t1=v1"},
   304  				[]string{"my-comp"},
   305  				map[string]map[setKey]string{
   306  					"comp": {
   307  						keyType:    "comp",
   308  						keyCPU:     "1",
   309  						keyMemory:  "2Gi",
   310  						keyStorage: "10Gi",
   311  					},
   312  				},
   313  				false,
   314  			},
   315  			// set more than one time
   316  			{
   317  				[]string{"cpu=1,memory=2Gi", "storage=10Gi,cpu=2"},
   318  				[]string{"my-comp"},
   319  				map[string]map[setKey]string{
   320  					"my-comp": {
   321  						keyCPU:     "2",
   322  						keyMemory:  "2Gi",
   323  						keyStorage: "10Gi",
   324  					},
   325  				},
   326  				true,
   327  			},
   328  			{
   329  				[]string{"type=my-comp,cpu=1,memory=2Gi", "storage=10Gi,cpu=2"},
   330  				[]string{"my-comp"},
   331  				map[string]map[setKey]string{
   332  					"my-comp": {
   333  						keyType:    "my-comp",
   334  						keyCPU:     "2",
   335  						keyMemory:  "2Gi",
   336  						keyStorage: "10Gi",
   337  					},
   338  				},
   339  				true,
   340  			},
   341  			{
   342  				[]string{"type=comp1,cpu=1,memory=2Gi,class=general-2c4g", "type=comp2,storage=10Gi,cpu=2,class=mo-1c8g,replicas=3"},
   343  				[]string{"comp1", "comp2"},
   344  				map[string]map[setKey]string{
   345  					"comp1": {
   346  						keyType:   "comp1",
   347  						keyCPU:    "1",
   348  						keyMemory: "2Gi",
   349  						keyClass:  "general-2c4g",
   350  					},
   351  					"comp2": {
   352  						keyType:     "comp2",
   353  						keyCPU:      "2",
   354  						keyStorage:  "10Gi",
   355  						keyClass:    "mo-1c8g",
   356  						keyReplicas: "3",
   357  					},
   358  				},
   359  				true,
   360  			},
   361  			{
   362  				[]string{"switchPolicy=MaximumAvailability"},
   363  				[]string{"my-comp"},
   364  				map[string]map[setKey]string{
   365  					"my-comp": {
   366  						keySwitchPolicy: "MaximumAvailability",
   367  					},
   368  				},
   369  				true,
   370  			},
   371  			{
   372  				[]string{"storageClass=test"},
   373  				[]string{"my-comp"},
   374  				map[string]map[setKey]string{
   375  					"my-comp": {
   376  						keyStorageClass: "test",
   377  					},
   378  				},
   379  				true,
   380  			},
   381  		}
   382  
   383  		for _, t := range testCases {
   384  			By(strings.Join(t.values, " "))
   385  			res, err := buildCompSetsMap(t.values, mockCD(t.compDefNames))
   386  			if t.success {
   387  				Expect(err).Should(Succeed())
   388  				Expect(reflect.DeepEqual(res, t.expected)).Should(BeTrue())
   389  			} else {
   390  				Expect(err).Should(HaveOccurred())
   391  			}
   392  		}
   393  	})
   394  
   395  	It("build tolerations", func() {
   396  		raw := []string{"engineType=mongo:NoSchedule"}
   397  		res, err := util.BuildTolerations(raw)
   398  		Expect(err).Should(BeNil())
   399  		Expect(len(res)).Should(Equal(1))
   400  	})
   401  
   402  	It("generate random cluster name", func() {
   403  		dynamic := testing.FakeDynamicClient()
   404  		name, err := generateClusterName(dynamic, "")
   405  		Expect(err).Should(Succeed())
   406  		Expect(name).ShouldNot(BeEmpty())
   407  	})
   408  
   409  	It("set backup", func() {
   410  		backupName := "test-backup"
   411  		clusterName := "test-cluster"
   412  		backup := testing.FakeBackup(backupName)
   413  		cluster := testing.FakeCluster("clusterName", testing.Namespace)
   414  		dynamic := testing.FakeDynamicClient(backup, cluster)
   415  		o := &CreateOptions{}
   416  		o.Dynamic = dynamic
   417  		o.Namespace = testing.Namespace
   418  		o.Backup = backupName
   419  		components := []map[string]interface{}{
   420  			{
   421  				"name": "mysql",
   422  			},
   423  		}
   424  		Expect(setBackup(o, components).Error()).Should(ContainSubstring("is not completed"))
   425  
   426  		By("test backup is completed")
   427  		mockBackupInfo(dynamic, backupName, clusterName, nil, "")
   428  		Expect(setBackup(o, components)).Should(Succeed())
   429  	})
   430  
   431  	It("test fillClusterMetadataFromBackup", func() {
   432  		baseBackupName := "test-backup"
   433  		logBackupName := "test-logfile-backup"
   434  		clusterName := testing.ClusterName
   435  		baseBackup := testing.FakeBackup(baseBackupName)
   436  		logfileBackup := testing.FakeBackup(logBackupName)
   437  		cluster := testing.FakeCluster("clusterName", testing.Namespace)
   438  		dynamic := testing.FakeDynamicClient(baseBackup, logfileBackup, cluster)
   439  
   440  		o := &CreateOptions{}
   441  		o.Dynamic = dynamic
   442  		o.Namespace = testing.Namespace
   443  		o.RestoreTime = "Jun 16,2023 18:57:01 UTC+0800"
   444  		o.Backup = logBackupName
   445  		backupLogTime, _ := util.TimeParse(o.RestoreTime, time.Second)
   446  		buildBackupLogTime := func(d time.Duration) string {
   447  			return backupLogTime.Add(d).Format(time.RFC3339)
   448  		}
   449  		buildTimeRange := func(startTime, stopTime string) map[string]any {
   450  			return map[string]any{
   451  				"start": startTime,
   452  				"end":   stopTime,
   453  			}
   454  		}
   455  		mockBackupInfo(dynamic, baseBackupName, clusterName, buildTimeRange(buildBackupLogTime(-30*time.Second), buildBackupLogTime(-10*time.Second)), "snapshot")
   456  		mockBackupInfo(dynamic, logBackupName, clusterName, buildTimeRange(buildBackupLogTime(-1*time.Minute), buildBackupLogTime(time.Minute)), "logfile")
   457  		By("fill cluster from backup success")
   458  		Expect(fillClusterInfoFromBackup(o, &cluster)).Should(Succeed())
   459  		Expect(cluster.Spec.ClusterDefRef).Should(Equal(testing.ClusterDefName))
   460  		Expect(cluster.Spec.ClusterVersionRef).Should(Equal(testing.ClusterVersionName))
   461  
   462  		By("fill cluster definition does not match")
   463  		o.ClusterDefRef = "test-not-match-cluster-definition"
   464  		Expect(fillClusterInfoFromBackup(o, &cluster)).Should(HaveOccurred())
   465  		o.ClusterDefRef = ""
   466  
   467  		By("fill cluster version does not match")
   468  		o.ClusterVersionRef = "test-not-match-cluster-version"
   469  		Expect(fillClusterInfoFromBackup(o, &cluster)).Should(HaveOccurred())
   470  	})
   471  
   472  	It("test build backup config", func() {
   473  		backupPolicyTemplate := testing.FakeBackupPolicyTemplate("backupPolicyTemplate-test", testing.ClusterDefName)
   474  		backupPolicy := appsv1alpha1.BackupPolicy{
   475  			BackupMethods: []appsv1alpha1.BackupMethod{
   476  				{
   477  					BackupMethod: v1alpha1.BackupMethod{
   478  						Name:            "volume-snapshot",
   479  						SnapshotVolumes: boolptr.True(),
   480  					},
   481  				},
   482  				{
   483  					BackupMethod: v1alpha1.BackupMethod{
   484  						Name: "xtrabackup",
   485  					},
   486  				},
   487  			},
   488  		}
   489  		backupPolicyTemplate.Spec.BackupPolicies = append(backupPolicyTemplate.Spec.BackupPolicies, backupPolicy)
   490  		dynamic := testing.FakeDynamicClient(backupPolicyTemplate)
   491  
   492  		o := &CreateOptions{}
   493  		o.Cmd = NewCreateCmd(o.Factory, o.IOStreams)
   494  		o.Dynamic = dynamic
   495  		o.ClusterDefRef = testing.ClusterDefName
   496  		cluster := testing.FakeCluster("clusterName", testing.Namespace)
   497  
   498  		By("test backup is not set")
   499  		Expect(o.buildBackupConfig(cluster)).To(Succeed())
   500  
   501  		By("test backup enable")
   502  		o.BackupEnabled = true
   503  		Expect(o.Cmd.Flags().Set("backup-enabled", "true")).To(Succeed())
   504  		Expect(o.buildBackupConfig(cluster)).To(Succeed())
   505  		Expect(*o.BackupConfig.Enabled).Should(BeTrue())
   506  		Expect(o.BackupConfig.Method).Should(Equal("volume-snapshot"))
   507  
   508  		By("test backup with invalid method")
   509  		o.BackupMethod = "invalid-method"
   510  		Expect(o.Cmd.Flags().Set("backup-method", "invalid-method")).To(Succeed())
   511  		Expect(o.buildBackupConfig(cluster)).To(HaveOccurred())
   512  
   513  		By("test backup with xtrabackup method")
   514  		o.BackupMethod = "xtrabackup"
   515  		Expect(o.Cmd.Flags().Set("backup-method", "xtrabackup")).To(Succeed())
   516  		Expect(o.buildBackupConfig(cluster)).To(Succeed())
   517  		Expect(o.BackupConfig.Method).Should(Equal("xtrabackup"))
   518  
   519  		By("test backup is with wrong cron expression")
   520  		o.BackupCronExpression = "wrong-cron-expression"
   521  		Expect(o.Cmd.Flags().Set("backup-cron-expression", "wrong-corn-expression"))
   522  		Expect(o.buildBackupConfig(cluster)).To(HaveOccurred())
   523  
   524  		By("test backup is with correct corn expression")
   525  		o.BackupCronExpression = "0 0 * * *"
   526  		Expect(o.Cmd.Flags().Set("backup-cron-expression", "0 0 * * *")).To(Succeed())
   527  		Expect(o.buildBackupConfig(cluster)).To(Succeed())
   528  		Expect(o.BackupConfig.CronExpression).Should(Equal("0 0 * * *"))
   529  	})
   530  
   531  	It("build multiple pvc in one cluster component", func() {
   532  		testCases := []struct {
   533  			pvcs         []string
   534  			compDefNames []string
   535  			expected     map[string][]map[storageKey]string
   536  			success      bool
   537  		}{
   538  			{
   539  				nil,
   540  				nil,
   541  				map[string][]map[storageKey]string{},
   542  				true,
   543  			},
   544  			// --pvc all key
   545  			{
   546  				[]string{"type=comp1,name=data,size=10Gi,storageClass=localPath,mode=ReadWriteOnce"},
   547  				[]string{"comp1", "comp2"},
   548  				map[string][]map[storageKey]string{
   549  					"comp1": {
   550  						map[storageKey]string{
   551  							storageKeyType:         "comp1",
   552  							storageKeyName:         "data",
   553  							storageKeySize:         "10Gi",
   554  							storageKeyStorageClass: "localPath",
   555  							storageAccessMode:      "ReadWriteOnce",
   556  						},
   557  					},
   558  				}, true,
   559  			},
   560  			// multiple components and don't specify the type,
   561  			// the default type will be the first component.
   562  			{
   563  				[]string{"name=data,size=10Gi,storageClass=localPath,mode=ReadWriteOnce"},
   564  				[]string{"comp1", "comp2"},
   565  				map[string][]map[storageKey]string{
   566  					"comp1": {
   567  						map[storageKey]string{
   568  							storageKeyName:         "data",
   569  							storageKeySize:         "10Gi",
   570  							storageKeyStorageClass: "localPath",
   571  							storageAccessMode:      "ReadWriteOnce",
   572  						},
   573  					},
   574  				}, true,
   575  			},
   576  			// wrong key
   577  			{
   578  				[]string{"cpu=1,memory=2Gi"},
   579  				[]string{"comp1"},
   580  				nil,
   581  				false,
   582  			},
   583  			// wrong component
   584  			{
   585  
   586  				[]string{"type=comp3,name=data,size=10Gi,storageClass=localPath,mode=ReadWriteOnce"},
   587  				[]string{"comp1", "comp2"},
   588  				nil,
   589  				false,
   590  			},
   591  			// one component with multiple pvc
   592  			{
   593  				[]string{"type=comp1,name=data,size=10Gi,storageClass=localPath,mode=ReadWriteOnce", "type=comp1,name=log,size=5Gi,storageClass=localPath,mode=ReadWriteMany"},
   594  				[]string{"comp1"},
   595  				map[string][]map[storageKey]string{
   596  					"comp1": {
   597  						map[storageKey]string{
   598  							storageKeyType:         "comp1",
   599  							storageKeyName:         "data",
   600  							storageKeySize:         "10Gi",
   601  							storageKeyStorageClass: "localPath",
   602  							storageAccessMode:      "ReadWriteOnce",
   603  						},
   604  						map[storageKey]string{
   605  							storageKeyType:         "comp1",
   606  							storageKeyName:         "log",
   607  							storageKeySize:         "5Gi",
   608  							storageKeyStorageClass: "localPath",
   609  							storageAccessMode:      "ReadWriteMany",
   610  						},
   611  					},
   612  				}, true,
   613  			},
   614  			// multiple components with one pvc
   615  			// it has the same effect as "--set type=comp1,storage=10Gi --set type=comp2,storage=5Gi"
   616  			{
   617  				[]string{"type=comp1,name=data,size=10Gi", "type=comp2,name=data,size=5Gi"},
   618  				[]string{"comp1", "comp2", "comp3"},
   619  				map[string][]map[storageKey]string{
   620  					"comp1": {
   621  						map[storageKey]string{
   622  							storageKeyType: "comp1",
   623  							storageKeyName: "data",
   624  							storageKeySize: "10Gi",
   625  						},
   626  					},
   627  					"comp2": {
   628  						map[storageKey]string{
   629  							storageKeyType: "comp2",
   630  							storageKeyName: "data",
   631  							storageKeySize: "5Gi",
   632  						},
   633  					},
   634  				}, true,
   635  			},
   636  			// multiple components, and some component with multiple pvcs
   637  			{
   638  				[]string{"type=comp1,name=data,size=10Gi", "type=comp1,name=log,size=5Gi", "type=comp2,name=data,size=5Gi"},
   639  				[]string{"comp1", "comp2", "comp3"}, map[string][]map[storageKey]string{
   640  					"comp1": {
   641  						map[storageKey]string{
   642  							storageKeyType: "comp1",
   643  							storageKeyName: "data",
   644  							storageKeySize: "10Gi",
   645  						},
   646  						map[storageKey]string{
   647  							storageKeyType: "comp1",
   648  							storageKeyName: "log",
   649  							storageKeySize: "5Gi",
   650  						},
   651  					},
   652  					"comp2": {
   653  						map[storageKey]string{
   654  							storageKeyType: "comp2",
   655  							storageKeyName: "data",
   656  							storageKeySize: "5Gi",
   657  						},
   658  					},
   659  				}, true,
   660  			},
   661  		}
   662  
   663  		for _, t := range testCases {
   664  			By(strings.Join(t.pvcs, " "))
   665  			res, err := buildCompStorages(t.pvcs, mockCD(t.compDefNames))
   666  			if t.success {
   667  				Expect(err).Should(Succeed())
   668  				Expect(reflect.DeepEqual(res, t.expected)).Should(BeTrue())
   669  			} else {
   670  				Expect(err).Should(HaveOccurred())
   671  			}
   672  		}
   673  	})
   674  
   675  	It("rebuild clusterComponentSpec VolumeClaimTemplates by --pvc", func() {
   676  		comps, err := buildClusterComp(mockCD([]string{"comp1", "comp2"}), nil, clsMgr)
   677  
   678  		Expect(err).Should(Succeed())
   679  		Expect(comps).ShouldNot(BeNil())
   680  		testCases := []struct {
   681  			describe             string
   682  			pvcMaps              map[string][]map[storageKey]string
   683  			clusterComponentSpec []*appsv1alpha1.ClusterComponentSpec
   684  			expected             map[string][]appsv1alpha1.ClusterComponentVolumeClaimTemplate
   685  		}{
   686  			{"rebuild multiple pvc in one component",
   687  				map[string][]map[storageKey]string{
   688  					"comp1": {
   689  						map[storageKey]string{
   690  							storageKeyType: "comp1",
   691  							storageKeyName: "data",
   692  							storageKeySize: "10Gi",
   693  						},
   694  						map[storageKey]string{
   695  							storageKeyType: "comp1",
   696  							storageKeyName: "log",
   697  							storageKeySize: "5Gi",
   698  						},
   699  					},
   700  					"comp2": {
   701  						map[storageKey]string{
   702  							storageKeyType: "comp2",
   703  							storageKeyName: "data",
   704  							storageKeySize: "5Gi",
   705  						},
   706  					}},
   707  				comps,
   708  				map[string][]appsv1alpha1.ClusterComponentVolumeClaimTemplate{
   709  					"comp1": {
   710  						{
   711  							Name: "data",
   712  							Spec: appsv1alpha1.PersistentVolumeClaimSpec{
   713  								AccessModes: []corev1.PersistentVolumeAccessMode{
   714  									corev1.ReadWriteOnce,
   715  								},
   716  								Resources: corev1.ResourceRequirements{
   717  									Requests: corev1.ResourceList{
   718  										corev1.ResourceStorage: resource.MustParse("10Gi"),
   719  									},
   720  								},
   721  							},
   722  						},
   723  						{
   724  							Name: "log",
   725  							Spec: appsv1alpha1.PersistentVolumeClaimSpec{
   726  								AccessModes: []corev1.PersistentVolumeAccessMode{
   727  									corev1.ReadWriteOnce,
   728  								},
   729  								Resources: corev1.ResourceRequirements{
   730  									Requests: corev1.ResourceList{
   731  										corev1.ResourceStorage: resource.MustParse("5Gi"),
   732  									},
   733  								},
   734  							},
   735  						},
   736  					},
   737  					"comp2": {
   738  						{
   739  							Name: "data",
   740  							Spec: appsv1alpha1.PersistentVolumeClaimSpec{
   741  								AccessModes: []corev1.PersistentVolumeAccessMode{
   742  									corev1.ReadWriteOnce,
   743  								},
   744  								Resources: corev1.ResourceRequirements{
   745  									Requests: corev1.ResourceList{
   746  										corev1.ResourceStorage: resource.MustParse("5Gi"),
   747  									},
   748  								},
   749  							},
   750  						},
   751  					},
   752  				},
   753  			},
   754  		}
   755  
   756  		for _, t := range testCases {
   757  			By(t.describe)
   758  			res := rebuildCompStorage(t.pvcMaps, t.clusterComponentSpec)
   759  			for _, spec := range res {
   760  				Expect(reflect.DeepEqual(spec.VolumeClaimTemplates, t.expected[spec.Name])).Should(BeTrue())
   761  			}
   762  		}
   763  
   764  	})
   765  
   766  	It("test getServiceRefs", func() {
   767  		testCase := []struct {
   768  			input    []string
   769  			success  bool
   770  			expected []map[serviceRefKey]string
   771  		}{
   772  			{
   773  				[]string{fmt.Sprintf("name=%s,cluster=mysql,namespace=default", testing.ServiceRefName)},
   774  				true,
   775  				[]map[serviceRefKey]string{
   776  					{
   777  						serviceRefKeyName:      testing.ServiceRefName,
   778  						serviceRefKeyCluster:   "mysql",
   779  						serviceRefKeyNamespace: "default",
   780  					},
   781  				},
   782  			},
   783  			{
   784  				[]string{"name=invalid,cluster=mysql,namespace=default"},
   785  				false,
   786  				nil,
   787  			},
   788  			{
   789  				[]string{"invalidKey=test"},
   790  				false,
   791  				nil,
   792  			},
   793  		}
   794  		for i := range testCase {
   795  			refs, err := getServiceRefs(testCase[i].input, testing.FakeClusterDef())
   796  			if testCase[i].success {
   797  				Expect(err).Should(Succeed())
   798  				Expect(refs).Should(Equal(testCase[i].expected))
   799  			} else {
   800  				Expect(err).Should(HaveOccurred())
   801  			}
   802  		}
   803  
   804  	})
   805  
   806  	It("test build ServiceRefs for ClusterComponentSpec", func() {
   807  		testCase := []struct {
   808  			input    []string
   809  			before   []*appsv1alpha1.ClusterComponentSpec
   810  			cd       *appsv1alpha1.ClusterDefinition
   811  			success  bool
   812  			expected []*appsv1alpha1.ClusterComponentSpec
   813  		}{
   814  			{[]string{fmt.Sprintf("name=%s,cluster=%s,namespace=%s", testing.ServiceRefName, testing.ClusterName, testing.Namespace)},
   815  				[]*appsv1alpha1.ClusterComponentSpec{
   816  					{
   817  						Name: testing.ComponentDefName,
   818  					},
   819  				},
   820  				testing.FakeClusterDef(),
   821  				true,
   822  				[]*appsv1alpha1.ClusterComponentSpec{
   823  					{
   824  						Name: testing.ComponentDefName,
   825  						ServiceRefs: []appsv1alpha1.ServiceRef{
   826  							{Name: testing.ServiceRefName, Cluster: testing.ClusterName, Namespace: testing.Namespace},
   827  						},
   828  					},
   829  				},
   830  			}, {[]string{fmt.Sprintf("name=%s,cluster=%s,namespace=%s", testing.ServiceRefName, testing.ClusterName, testing.Namespace)},
   831  				[]*appsv1alpha1.ClusterComponentSpec{
   832  					{
   833  						Name: testing.ComponentDefName,
   834  					},
   835  				},
   836  				testing.FakeClusterDef(),
   837  				true,
   838  				[]*appsv1alpha1.ClusterComponentSpec{
   839  					{
   840  						Name: testing.ComponentDefName,
   841  						ServiceRefs: []appsv1alpha1.ServiceRef{
   842  							{Name: testing.ServiceRefName, Cluster: testing.ClusterName, Namespace: testing.Namespace},
   843  						},
   844  					},
   845  				},
   846  			},
   847  		}
   848  
   849  		for i := range testCase {
   850  			compSpec, err := buildServiceRefs(testCase[i].input, testCase[i].cd, testCase[i].before)
   851  			if testCase[i].success {
   852  				Expect(err).Should(Succeed())
   853  				Expect(compSpec).Should(Equal(testCase[i].expected))
   854  			} else {
   855  				Expect(err).Should(HaveOccurred())
   856  			}
   857  		}
   858  
   859  	})
   860  })