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 }