github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/class/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 class 21 22 import ( 23 "bytes" 24 "fmt" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 "k8s.io/cli-runtime/pkg/genericiooptions" 29 30 "k8s.io/client-go/kubernetes/scheme" 31 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 32 33 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 34 "github.com/1aal/kubeblocks/pkg/cli/testing" 35 ) 36 37 var _ = Describe("create", func() { 38 var ( 39 createOptions *CreateOptions 40 out *bytes.Buffer 41 tf *cmdtesting.TestFactory 42 streams genericiooptions.IOStreams 43 ) 44 45 fillResources := func(o *CreateOptions, cpu string, memory string) { 46 o.CPU = cpu 47 o.Memory = memory 48 o.ClassName = fmt.Sprintf("custom-%s-%s", cpu, memory) 49 } 50 51 BeforeEach(func() { 52 streams, _, out, _ = genericiooptions.NewTestIOStreams() 53 tf = testing.NewTestFactory(namespace) 54 _ = appsv1alpha1.AddToScheme(scheme.Scheme) 55 createOptions = &CreateOptions{ 56 IOStreams: streams, 57 ClusterDefRef: "apecloud-mysql", 58 ComponentType: "mysql", 59 } 60 }) 61 62 AfterEach(func() { 63 tf.Cleanup() 64 }) 65 66 It("should succeed to new command", func() { 67 cmd := NewCreateCommand(tf, streams) 68 Expect(cmd).ShouldNot(BeNil()) 69 }) 70 71 Context("with resource arguments", func() { 72 73 Context("without constraints", func() { 74 BeforeEach(func() { 75 tf.FakeDynamicClient = testing.FakeDynamicClient(&classDef, &generalResourceConstraintWithoutSelector) 76 createOptions.Factory = tf 77 Expect(createOptions.complete(tf)).ShouldNot(HaveOccurred()) 78 }) 79 80 It("should succeed if component don't have any constraints", func() { 81 fillResources(createOptions, "1", "100Gi") 82 Expect(createOptions.run()).ShouldNot(HaveOccurred()) 83 }) 84 85 }) 86 87 Context("with constraints", func() { 88 BeforeEach(func() { 89 tf.FakeDynamicClient = testing.FakeDynamicClient(&classDef, &generalResourceConstraint, &memoryOptimizedResourceConstraint) 90 createOptions.Factory = tf 91 Expect(createOptions.complete(tf)).ShouldNot(HaveOccurred()) 92 }) 93 94 It("should fail if required arguments is missing", func() { 95 fillResources(createOptions, "", "48Gi") 96 Expect(createOptions.validate([]string{"general-12c48g"})).Should(HaveOccurred()) 97 fillResources(createOptions, "12", "") 98 Expect(createOptions.validate([]string{"general-12c48g"})).Should(HaveOccurred()) 99 fillResources(createOptions, "12", "48g") 100 Expect(createOptions.validate([]string{})).Should(HaveOccurred()) 101 }) 102 103 It("should succeed with required arguments", func() { 104 fillResources(createOptions, "96", "384Gi") 105 Expect(createOptions.validate([]string{"general-96c384g"})).ShouldNot(HaveOccurred()) 106 Expect(createOptions.run()).ShouldNot(HaveOccurred()) 107 Expect(out.String()).Should(ContainSubstring(createOptions.ClassName)) 108 }) 109 110 It("should fail if not conformed to constraint", func() { 111 By("memory not conformed to constraint") 112 fillResources(createOptions, "2", "9Gi") 113 Expect(createOptions.run()).Should(HaveOccurred()) 114 115 By("CPU with invalid step") 116 fillResources(createOptions, "0.6", "0.6Gi") 117 Expect(createOptions.run()).Should(HaveOccurred()) 118 }) 119 120 It("should fail if class name is conflicted", func() { 121 // class may be conflict only within the same object, so we set the objectName to be consistent with the name of the object classDef 122 createOptions.objectName = "kb.classes.default.apecloud-mysql.mysql" 123 124 fillResources(createOptions, "1", "1Gi") 125 createOptions.ClassName = "general-1c1g" 126 Expect(createOptions.run()).Should(HaveOccurred()) 127 128 fillResources(createOptions, "0.5", "0.5Gi") 129 Expect(createOptions.run()).ShouldNot(HaveOccurred()) 130 131 fillResources(createOptions, "0.5", "0.5Gi") 132 Expect(createOptions.run()).Should(HaveOccurred()) 133 }) 134 }) 135 }) 136 137 Context("with class definitions file", func() { 138 BeforeEach(func() { 139 tf.FakeDynamicClient = testing.FakeDynamicClient(&classDef, &generalResourceConstraint, &memoryOptimizedResourceConstraint) 140 createOptions.Factory = tf 141 Expect(createOptions.complete(tf)).ShouldNot(HaveOccurred()) 142 }) 143 144 It("should succeed", func() { 145 createOptions.File = testCustomClassDefsPath 146 Expect(createOptions.run()).ShouldNot(HaveOccurred()) 147 Expect(out.String()).Should(ContainSubstring("custom-1c1g")) 148 Expect(out.String()).Should(ContainSubstring("custom-4c16g")) 149 // memory optimized classes 150 Expect(out.String()).Should(ContainSubstring("custom-2c16g")) 151 Expect(out.String()).Should(ContainSubstring("custom-4c64g")) 152 }) 153 154 }) 155 156 })