github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/class/class_utils_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 . "github.com/onsi/ginkgo/v2" 24 . "github.com/onsi/gomega" 25 26 corev1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 29 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 30 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 31 ) 32 33 var _ = Describe("utils", func() { 34 buildResource := func(cpu string, memory string) corev1.ResourceList { 35 result := make(corev1.ResourceList) 36 if cpu != "" { 37 result[corev1.ResourceCPU] = resource.MustParse(cpu) 38 } 39 if memory != "" { 40 result[corev1.ResourceMemory] = resource.MustParse(memory) 41 } 42 return result 43 } 44 45 buildClass := func(name string, cpu string, mem string) v1alpha1.ComponentClass { 46 return v1alpha1.ComponentClass{ 47 Name: name, 48 CPU: resource.MustParse(cpu), 49 Memory: resource.MustParse(mem), 50 } 51 } 52 53 Context("validate component class", func() { 54 var ( 55 kbClassDefinitionObjName = "kb" 56 customClassDefinitionObjName = "custom" 57 clusterDefinitionName = "apecloud-mysql" 58 compType1 = "component-have-class-definition" 59 compType2 = "component-does-not-have-class-definition" 60 clsMgr *Manager 61 ) 62 63 BeforeEach(func() { 64 var err error 65 kbClassFactory := testapps.NewComponentClassDefinitionFactory(kbClassDefinitionObjName, clusterDefinitionName, compType1) 66 kbClassFactory.AddClasses([]v1alpha1.ComponentClass{ 67 buildClass("general-1c1g", "1", "1Gi"), 68 buildClass("general-1c4g", "1", "4Gi"), 69 buildClass("general-2c4g", "2", "4Gi"), 70 buildClass("general-2c8g", "2", "8Gi"), 71 buildClass("large", "500", "1000Gi"), 72 }) 73 74 customClassFactory := testapps.NewComponentClassDefinitionFactory(customClassDefinitionObjName, clusterDefinitionName, compType1) 75 customClassFactory.AddClasses([]v1alpha1.ComponentClass{ 76 buildClass("large", "100", "200Gi"), 77 }) 78 79 constraint := testapps.NewComponentResourceConstraintFactory(testapps.DefaultResourceConstraintName). 80 AddConstraints(testapps.GeneralResourceConstraint). 81 GetObject() 82 83 clsMgr, err = NewManager(v1alpha1.ComponentClassDefinitionList{ 84 Items: []v1alpha1.ComponentClassDefinition{ 85 *kbClassFactory.GetObject(), 86 *customClassFactory.GetObject(), 87 }, 88 }, v1alpha1.ComponentResourceConstraintList{Items: []v1alpha1.ComponentResourceConstraint{*constraint}}) 89 90 Expect(err).ShouldNot(HaveOccurred()) 91 }) 92 93 When("component have class definition", func() { 94 It("should succeed with valid classDefRef", func() { 95 comp := &v1alpha1.ClusterComponentSpec{ 96 ComponentDefRef: compType1, 97 ClassDefRef: &v1alpha1.ClassDefRef{ 98 Name: kbClassDefinitionObjName, 99 Class: testapps.Class1c1g.Name, 100 }, 101 } 102 cls, err := clsMgr.ChooseClass(comp) 103 Expect(err).ShouldNot(HaveOccurred()) 104 Expect(cls.CPU.Equal(testapps.Class1c1g.CPU)).Should(BeTrue()) 105 Expect(cls.Memory.Equal(testapps.Class1c1g.Memory)).Should(BeTrue()) 106 comp.Resources = cls.ToResourceRequirements() 107 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 108 }) 109 110 It("should match minial class with partial classDefRef", func() { 111 comp := &v1alpha1.ClusterComponentSpec{ 112 ComponentDefRef: compType1, 113 ClassDefRef: &v1alpha1.ClassDefRef{ 114 Class: "large", 115 }, 116 } 117 cls, err := clsMgr.ChooseClass(comp) 118 Expect(err).ShouldNot(HaveOccurred()) 119 Expect(cls.CPU.String()).Should(Equal("100")) 120 Expect(cls.Memory.String()).Should(Equal("200Gi")) 121 comp.Resources = cls.ToResourceRequirements() 122 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 123 }) 124 125 It("should fail with invalid classDefRef", func() { 126 comp := &v1alpha1.ClusterComponentSpec{ 127 ComponentDefRef: compType1, 128 ClassDefRef: &v1alpha1.ClassDefRef{Class: "class-not-exists"}, 129 } 130 _, err := clsMgr.ChooseClass(comp) 131 Expect(err).Should(HaveOccurred()) 132 }) 133 134 It("should succeed with valid resource", func() { 135 comp := &v1alpha1.ClusterComponentSpec{ 136 ComponentDefRef: compType1, 137 Resources: corev1.ResourceRequirements{ 138 Requests: buildResource("1", "1Gi"), 139 }, 140 } 141 cls, err := clsMgr.ChooseClass(comp) 142 Expect(err).ShouldNot(HaveOccurred()) 143 Expect(cls.CPU.Equal(testapps.Class1c1g.CPU)).Should(BeTrue()) 144 Expect(cls.Memory.Equal(testapps.Class1c1g.Memory)).Should(BeTrue()) 145 comp.Resources = cls.ToResourceRequirements() 146 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 147 }) 148 149 It("should fail with invalid cpu resource", func() { 150 comp := &v1alpha1.ClusterComponentSpec{ 151 ComponentDefRef: compType1, 152 Resources: corev1.ResourceRequirements{ 153 Requests: buildResource("100", "2Gi"), 154 }, 155 } 156 _, err := clsMgr.ChooseClass(comp) 157 Expect(err).Should(HaveOccurred()) 158 }) 159 160 It("should fail with invalid memory resource", func() { 161 comp := &v1alpha1.ClusterComponentSpec{ 162 ComponentDefRef: compType1, 163 Resources: corev1.ResourceRequirements{ 164 Requests: buildResource("1", "200Gi"), 165 }, 166 } 167 _, err := clsMgr.ChooseClass(comp) 168 Expect(err).Should(HaveOccurred()) 169 }) 170 171 It("should match minial class with empty resource", func() { 172 comp := &v1alpha1.ClusterComponentSpec{ 173 ComponentDefRef: compType1, 174 } 175 cls, err := clsMgr.ChooseClass(comp) 176 Expect(err).ShouldNot(HaveOccurred()) 177 Expect(cls.CPU.String()).Should(Equal("1")) 178 Expect(cls.Memory.String()).Should(Equal("1Gi")) 179 comp.Resources = cls.ToResourceRequirements() 180 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 181 }) 182 183 It("should match minial memory if with only cpu", func() { 184 comp := &v1alpha1.ClusterComponentSpec{ 185 ComponentDefRef: compType1, 186 Resources: corev1.ResourceRequirements{ 187 Requests: buildResource("2", ""), 188 }, 189 } 190 cls, err := clsMgr.ChooseClass(comp) 191 Expect(err).ShouldNot(HaveOccurred()) 192 Expect(cls.CPU.String()).Should(Equal("2")) 193 Expect(cls.Memory.String()).Should(Equal("4Gi")) 194 comp.Resources = cls.ToResourceRequirements() 195 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 196 }) 197 198 It("should match minial cpu if with only memory", func() { 199 comp := &v1alpha1.ClusterComponentSpec{ 200 ComponentDefRef: compType1, 201 Resources: corev1.ResourceRequirements{ 202 Requests: buildResource("", "4Gi"), 203 }, 204 } 205 cls, err := clsMgr.ChooseClass(comp) 206 Expect(err).ShouldNot(HaveOccurred()) 207 Expect(cls.CPU.String()).Should(Equal("1")) 208 Expect(cls.Memory.String()).Should(Equal("4Gi")) 209 comp.Resources = cls.ToResourceRequirements() 210 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 211 }) 212 }) 213 214 When("component without class definition", func() { 215 It("should succeed without classDefRef", func() { 216 comp := &v1alpha1.ClusterComponentSpec{ 217 ComponentDefRef: compType2, 218 } 219 cls, err := clsMgr.ChooseClass(comp) 220 Expect(err).ShouldNot(HaveOccurred()) 221 Expect(cls).Should(BeNil()) 222 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 223 }) 224 225 It("should fail with classDefRef", func() { 226 comp := &v1alpha1.ClusterComponentSpec{ 227 ComponentDefRef: compType2, 228 ClassDefRef: &v1alpha1.ClassDefRef{Class: testapps.Class1c1gName}, 229 } 230 _, err := clsMgr.ChooseClass(comp) 231 Expect(err).Should(HaveOccurred()) 232 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).Should(HaveOccurred()) 233 }) 234 235 It("should succeed without classDefRef", func() { 236 comp := &v1alpha1.ClusterComponentSpec{ 237 ComponentDefRef: compType2, 238 Resources: corev1.ResourceRequirements{ 239 Requests: buildResource("100", "200Gi"), 240 }, 241 } 242 cls, err := clsMgr.ChooseClass(comp) 243 Expect(err).ShouldNot(HaveOccurred()) 244 Expect(cls).Should(BeNil()) 245 Expect(clsMgr.ValidateResources(clusterDefinitionName, comp)).ShouldNot(HaveOccurred()) 246 }) 247 }) 248 }) 249 250 It("get classes should succeed", func() { 251 var ( 252 err error 253 classDefinitionObjName = "custom" 254 specClassDefRef = v1alpha1.ClassDefRef{Name: classDefinitionObjName, Class: testapps.Class1c1gName} 255 statusClassDefRef = v1alpha1.ClassDefRef{Name: classDefinitionObjName, Class: "general-100c100g"} 256 clsMgr *Manager 257 compType = "mysql" 258 ) 259 260 classDef := testapps.NewComponentClassDefinitionFactory(classDefinitionObjName, "apecloud-mysql", compType). 261 AddClasses([]v1alpha1.ComponentClass{testapps.Class1c1g}). 262 GetObject() 263 264 By("class definition status is out of date") 265 classDef.SetGeneration(1) 266 classDef.Status.ObservedGeneration = 0 267 classDef.Status.Classes = []v1alpha1.ComponentClass{ 268 { 269 Name: statusClassDefRef.Class, 270 CPU: resource.MustParse("100"), 271 Memory: resource.MustParse("100Gi"), 272 }, 273 } 274 clsMgr, err = NewManager(v1alpha1.ComponentClassDefinitionList{ 275 Items: []v1alpha1.ComponentClassDefinition{*classDef}, 276 }, v1alpha1.ComponentResourceConstraintList{}) 277 Expect(err).ShouldNot(HaveOccurred()) 278 Expect(clsMgr.HasClass(compType, specClassDefRef)).Should(BeTrue()) 279 Expect(clsMgr.HasClass(compType, statusClassDefRef)).Should(BeFalse()) 280 281 By("class definition status is in sync with the class definition spec") 282 classDef.Status.ObservedGeneration = 1 283 clsMgr, err = NewManager(v1alpha1.ComponentClassDefinitionList{ 284 Items: []v1alpha1.ComponentClassDefinition{*classDef}, 285 }, v1alpha1.ComponentResourceConstraintList{}) 286 Expect(err).ShouldNot(HaveOccurred()) 287 Expect(clsMgr.HasClass(compType, specClassDefRef)).Should(BeFalse()) 288 Expect(clsMgr.HasClass(compType, statusClassDefRef)).Should(BeTrue()) 289 }) 290 })