github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/apis/apps/v1alpha1/componentresourceconstraint_types.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1alpha1
    18  
    19  import (
    20  	"strings"
    21  
    22  	"golang.org/x/exp/slices"
    23  	"gopkg.in/inf.v0"
    24  	corev1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/resource"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  )
    28  
    29  // ComponentResourceConstraintSpec defines the desired state of ComponentResourceConstraint
    30  type ComponentResourceConstraintSpec struct {
    31  	// Component resource constraint rules.
    32  	// +patchMergeKey=name
    33  	// +patchStrategy=merge,retainKeys
    34  	// +listType=map
    35  	// +listMapKey=name
    36  	// +kubebuilder:validation:Required
    37  	Rules []ResourceConstraintRule `json:"rules"`
    38  
    39  	// selector is used to bind the resource constraint to cluster definitions.
    40  	// +listType=map
    41  	// +listMapKey=clusterDefRef
    42  	// +optional
    43  	Selector []ClusterResourceConstraintSelector `json:"selector,omitempty"`
    44  }
    45  
    46  type ClusterResourceConstraintSelector struct {
    47  	// clusterDefRef is the name of the cluster definition.
    48  	// +kubebuilder:validation:Required
    49  	ClusterDefRef string `json:"clusterDefRef"`
    50  
    51  	// selector is used to bind the resource constraint to components.
    52  	// +listType=map
    53  	// +listMapKey=componentDefRef
    54  	// +kubebuilder:validation:Required
    55  	Components []ComponentResourceConstraintSelector `json:"components"`
    56  }
    57  
    58  type ComponentResourceConstraintSelector struct {
    59  	// componentDefRef is the name of the component definition in the cluster definition.
    60  	// +kubebuilder:validation:Required
    61  	ComponentDefRef string `json:"componentDefRef"`
    62  
    63  	// rules are the constraint rules that will be applied to the component.
    64  	// +kubebuilder:validation:Required
    65  	Rules []string `json:"rules"`
    66  }
    67  
    68  type ResourceConstraintRule struct {
    69  	// The name of the constraint.
    70  	// +kubebuilder:validation:Required
    71  	Name string `json:"name"`
    72  
    73  	// The constraint for vcpu cores.
    74  	// +kubebuilder:validation:Required
    75  	CPU CPUConstraint `json:"cpu"`
    76  
    77  	// The constraint for memory size.
    78  	// +kubebuilder:validation:Required
    79  	Memory MemoryConstraint `json:"memory"`
    80  
    81  	// The constraint for storage size.
    82  	// +optional
    83  	Storage StorageConstraint `json:"storage"`
    84  }
    85  
    86  type CPUConstraint struct {
    87  	// The maximum count of vcpu cores, [Min, Max] defines a range for valid vcpu cores, and the value in this range
    88  	// must be multiple times of Step. It's useful to define a large number of valid values without defining them one by
    89  	// one. Please see the documentation for Step for some examples.
    90  	// If Slots is specified, Max, Min, and Step are ignored
    91  	// +optional
    92  	Max *resource.Quantity `json:"max,omitempty"`
    93  
    94  	// The minimum count of vcpu cores, [Min, Max] defines a range for valid vcpu cores, and the value in this range
    95  	// must be multiple times of Step. It's useful to define a large number of valid values without defining them one by
    96  	// one. Please see the documentation for Step for some examples.
    97  	// If Slots is specified, Max, Min, and Step are ignored
    98  	// +optional
    99  	Min *resource.Quantity `json:"min,omitempty"`
   100  
   101  	// The minimum granularity of vcpu cores, [Min, Max] defines a range for valid vcpu cores and the value in this range must be
   102  	// multiple times of Step.
   103  	// For example:
   104  	// 1. Min is 2, Max is 8, Step is 2, and the valid vcpu core is {2, 4, 6, 8}.
   105  	// 2. Min is 0.5, Max is 2, Step is 0.5, and the valid vcpu core is {0.5, 1, 1.5, 2}.
   106  	// +optional
   107  	Step *resource.Quantity `json:"step,omitempty"`
   108  
   109  	// The valid vcpu cores, it's useful if you want to define valid vcpu cores explicitly.
   110  	// If Slots is specified, Max, Min, and Step are ignored
   111  	// +optional
   112  	Slots []resource.Quantity `json:"slots,omitempty"`
   113  }
   114  
   115  type MemoryConstraint struct {
   116  	// The size of memory per vcpu core.
   117  	// For example: 1Gi, 200Mi.
   118  	// If SizePerCPU is specified, MinPerCPU and MaxPerCPU are ignore.
   119  	// +optional
   120  	SizePerCPU *resource.Quantity `json:"sizePerCPU,omitempty"`
   121  
   122  	// The maximum size of memory per vcpu core, [MinPerCPU, MaxPerCPU] defines a range for valid memory size per vcpu core.
   123  	// It is useful on GCP as the ratio between the CPU and memory may be a range.
   124  	// If SizePerCPU is specified, MinPerCPU and MaxPerCPU are ignored.
   125  	// Reference: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types
   126  	// +optional
   127  	MaxPerCPU *resource.Quantity `json:"maxPerCPU,omitempty"`
   128  
   129  	// The minimum size of memory per vcpu core, [MinPerCPU, MaxPerCPU] defines a range for valid memory size per vcpu core.
   130  	// It is useful on GCP as the ratio between the CPU and memory may be a range.
   131  	// If SizePerCPU is specified, MinPerCPU and MaxPerCPU are ignored.
   132  	// Reference: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types
   133  	// +optional
   134  	MinPerCPU *resource.Quantity `json:"minPerCPU,omitempty"`
   135  }
   136  
   137  type StorageConstraint struct {
   138  	// The minimum size of storage.
   139  	// +kubebuilder:default="20Gi"
   140  	// +optional
   141  	Min *resource.Quantity `json:"min,omitempty"`
   142  
   143  	// The maximum size of storage.
   144  	// +kubebuilder:default="10Ti"
   145  	// +optional
   146  	Max *resource.Quantity `json:"max,omitempty"`
   147  }
   148  
   149  // +genclient
   150  // +genclient:nonNamespaced
   151  // +k8s:openapi-gen=true
   152  // +kubebuilder:object:root=true
   153  // +kubebuilder:resource:categories={kubeblocks,all},scope=Cluster,shortName=crc
   154  
   155  // ComponentResourceConstraint is the Schema for the componentresourceconstraints API
   156  type ComponentResourceConstraint struct {
   157  	metav1.TypeMeta   `json:",inline"`
   158  	metav1.ObjectMeta `json:"metadata,omitempty"`
   159  
   160  	Spec ComponentResourceConstraintSpec `json:"spec,omitempty"`
   161  }
   162  
   163  // +kubebuilder:object:root=true
   164  
   165  // ComponentResourceConstraintList contains a list of ComponentResourceConstraint
   166  type ComponentResourceConstraintList struct {
   167  	metav1.TypeMeta `json:",inline"`
   168  	metav1.ListMeta `json:"metadata,omitempty"`
   169  	Items           []ComponentResourceConstraint `json:"items"`
   170  }
   171  
   172  func init() {
   173  	SchemeBuilder.Register(&ComponentResourceConstraint{}, &ComponentResourceConstraintList{})
   174  }
   175  
   176  // ValidateCPU validates if the CPU meets the constraint
   177  func (m *ResourceConstraintRule) ValidateCPU(cpu *resource.Quantity) bool {
   178  	if cpu.IsZero() {
   179  		return true
   180  	}
   181  	if m.CPU.Min != nil && m.CPU.Min.Cmp(*cpu) > 0 {
   182  		return false
   183  	}
   184  	if m.CPU.Max != nil && m.CPU.Max.Cmp(*cpu) < 0 {
   185  		return false
   186  	}
   187  	if m.CPU.Step != nil {
   188  		result := inf.NewDec(1, 0).QuoExact(cpu.AsDec(), m.CPU.Step.AsDec())
   189  		if result == nil {
   190  			return false
   191  		}
   192  		// the quotient must be an integer
   193  		if strings.Contains(strings.TrimRight(result.String(), ".0"), ".") {
   194  			return false
   195  		}
   196  	}
   197  	if m.CPU.Slots != nil && slices.Index(m.CPU.Slots, *cpu) < 0 {
   198  		return false
   199  	}
   200  	return true
   201  }
   202  
   203  // ValidateMemory validates if the memory meets the constraint
   204  func (m *ResourceConstraintRule) ValidateMemory(cpu *resource.Quantity, memory *resource.Quantity) bool {
   205  	if memory.IsZero() {
   206  		return true
   207  	}
   208  
   209  	var slots []resource.Quantity
   210  	switch {
   211  	case cpu != nil && !cpu.IsZero():
   212  		slots = append(slots, *cpu)
   213  	case len(m.CPU.Slots) > 0:
   214  		slots = m.CPU.Slots
   215  	default:
   216  		slot := *m.CPU.Min
   217  		for slot.Cmp(*m.CPU.Max) <= 0 {
   218  			slots = append(slots, slot)
   219  			slot = resource.MustParse(inf.NewDec(1, 0).Add(slot.AsDec(), m.CPU.Step.AsDec()).String())
   220  		}
   221  	}
   222  
   223  	for _, slot := range slots {
   224  		if m.Memory.SizePerCPU != nil && !m.Memory.SizePerCPU.IsZero() {
   225  			match := inf.NewDec(1, 0).Mul(slot.AsDec(), m.Memory.SizePerCPU.AsDec()).Cmp(memory.AsDec()) == 0
   226  			if match {
   227  				return true
   228  			}
   229  		} else {
   230  			maxMemory := inf.NewDec(1, 0).Mul(slot.AsDec(), m.Memory.MaxPerCPU.AsDec())
   231  			minMemory := inf.NewDec(1, 0).Mul(slot.AsDec(), m.Memory.MinPerCPU.AsDec())
   232  			if maxMemory.Cmp(memory.AsDec()) >= 0 && minMemory.Cmp(memory.AsDec()) <= 0 {
   233  				return true
   234  			}
   235  		}
   236  	}
   237  	return false
   238  }
   239  
   240  // ValidateStorage validates if the storage meets the constraint
   241  func (m *ResourceConstraintRule) ValidateStorage(storage *resource.Quantity) bool {
   242  	if storage.IsZero() {
   243  		return true
   244  	}
   245  
   246  	if m.Storage.Min != nil && m.Storage.Min.Cmp(*storage) > 0 {
   247  		return false
   248  	}
   249  	if m.Storage.Max != nil && m.Storage.Max.Cmp(*storage) < 0 {
   250  		return false
   251  	}
   252  	return true
   253  }
   254  
   255  // ValidateResources validates if the resources meets the constraint
   256  func (m *ResourceConstraintRule) ValidateResources(r corev1.ResourceList) bool {
   257  	if !m.ValidateCPU(r.Cpu()) {
   258  		return false
   259  	}
   260  
   261  	if !m.ValidateMemory(r.Cpu(), r.Memory()) {
   262  		return false
   263  	}
   264  
   265  	if !m.ValidateStorage(r.Storage()) {
   266  		return false
   267  	}
   268  
   269  	return true
   270  }
   271  
   272  func (m *ResourceConstraintRule) CompleteResources(r corev1.ResourceList) corev1.ResourceList {
   273  	if r.Cpu().IsZero() || !r.Memory().IsZero() {
   274  		return corev1.ResourceList{corev1.ResourceCPU: *r.Cpu(), corev1.ResourceMemory: *r.Memory()}
   275  	}
   276  
   277  	var memory *inf.Dec
   278  	if m.Memory.SizePerCPU != nil {
   279  		memory = inf.NewDec(1, 0).Mul(r.Cpu().AsDec(), m.Memory.SizePerCPU.AsDec())
   280  	} else {
   281  		memory = inf.NewDec(1, 0).Mul(r.Cpu().AsDec(), m.Memory.MinPerCPU.AsDec())
   282  	}
   283  	return corev1.ResourceList{
   284  		corev1.ResourceCPU:    *r.Cpu(),
   285  		corev1.ResourceMemory: resource.MustParse(memory.String()),
   286  	}
   287  }
   288  
   289  // GetMinimalResources gets the minimal resources meets the constraint
   290  func (m *ResourceConstraintRule) GetMinimalResources() corev1.ResourceList {
   291  	var (
   292  		minCPU    resource.Quantity
   293  		minMemory resource.Quantity
   294  	)
   295  
   296  	if len(m.CPU.Slots) == 0 && (m.CPU.Min == nil || m.CPU.Min.IsZero()) {
   297  		return corev1.ResourceList{
   298  			corev1.ResourceCPU:    resource.MustParse("1"),
   299  			corev1.ResourceMemory: resource.MustParse("1Gi"),
   300  		}
   301  	}
   302  
   303  	if len(m.CPU.Slots) > 0 {
   304  		minCPU = m.CPU.Slots[0]
   305  	}
   306  
   307  	if minCPU.IsZero() || (m.CPU.Min != nil && minCPU.Cmp(*m.CPU.Min) > 0) {
   308  		minCPU = *m.CPU.Min
   309  	}
   310  
   311  	var memory *inf.Dec
   312  	if m.Memory.MinPerCPU != nil {
   313  		memory = inf.NewDec(1, 0).Mul(minCPU.AsDec(), m.Memory.MinPerCPU.AsDec())
   314  	} else {
   315  		memory = inf.NewDec(1, 0).Mul(minCPU.AsDec(), m.Memory.SizePerCPU.AsDec())
   316  	}
   317  	minMemory = resource.MustParse(memory.String())
   318  	return corev1.ResourceList{corev1.ResourceCPU: minCPU, corev1.ResourceMemory: minMemory}
   319  }
   320  
   321  // FindMatchingRules find all constraint rules that resource satisfies.
   322  func (c *ComponentResourceConstraint) FindMatchingRules(
   323  	clusterDefRef string,
   324  	componentDefRef string,
   325  	resources corev1.ResourceList) []ResourceConstraintRule {
   326  
   327  	rules := c.FindRules(clusterDefRef, componentDefRef)
   328  	var result []ResourceConstraintRule
   329  	for _, rule := range rules {
   330  		if rule.ValidateResources(resources) {
   331  			result = append(result, rule)
   332  		}
   333  	}
   334  	return result
   335  }
   336  
   337  // MatchClass checks if the class meets the constraint rules.
   338  func (c *ComponentResourceConstraint) MatchClass(clusterDefRef, componentDefRef string, class *ComponentClass) bool {
   339  	request := corev1.ResourceList{
   340  		corev1.ResourceCPU:    class.CPU,
   341  		corev1.ResourceMemory: class.Memory,
   342  	}
   343  	constraints := c.FindMatchingRules(clusterDefRef, componentDefRef, request)
   344  	return len(constraints) > 0
   345  }
   346  
   347  // FindRules find all constraint rules that the component should conform to.
   348  func (c *ComponentResourceConstraint) FindRules(clusterDefRef, componentDefRef string) []ResourceConstraintRule {
   349  	rules := make(map[string]bool)
   350  	for _, selector := range c.Spec.Selector {
   351  		if selector.ClusterDefRef != clusterDefRef {
   352  			continue
   353  		}
   354  		for _, item := range selector.Components {
   355  			if item.ComponentDefRef != componentDefRef {
   356  				continue
   357  			}
   358  			for _, name := range item.Rules {
   359  				rules[name] = true
   360  			}
   361  		}
   362  	}
   363  
   364  	var result []ResourceConstraintRule
   365  	for _, rule := range c.Spec.Rules {
   366  		if _, ok := rules[rule.Name]; !ok {
   367  			continue
   368  		}
   369  		result = append(result, rule)
   370  	}
   371  	return result
   372  }