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 })