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