Skip to content

Commit

Permalink
chore: optimize class reference and add storage constraint
Browse files Browse the repository at this point in the history
  • Loading branch information
iziang committed Jun 18, 2023
1 parent f4eb2a5 commit d42964d
Show file tree
Hide file tree
Showing 37 changed files with 1,015 additions and 604 deletions.
12 changes: 12 additions & 0 deletions apis/apps/v1alpha1/componentclassdefinition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,15 @@ func (r *ComponentClass) ToResourceRequirements() corev1.ResourceRequirements {
}
return corev1.ResourceRequirements{Requests: requests, Limits: requests}
}

func (r *ComponentClassInstance) Cmp(b *ComponentClassInstance) int {
if out := r.CPU.Cmp(b.CPU); out != 0 {
return out
}

if out := r.Memory.Cmp(b.Memory); out != 0 {
return out
}

return 0
}
65 changes: 65 additions & 0 deletions apis/apps/v1alpha1/componentclassdefinition_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
Copyright (C) 2022-2023 ApeCloud Co., Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
)

func TestToResourceRequirements(t *testing.T) {
var (
cpu = resource.MustParse("1")
memory = resource.MustParse("1Gi")
)
cls := ComponentClass{
CPU: cpu,
Memory: memory,
}
rr := cls.ToResourceRequirements()
assert.True(t, rr.Requests.Cpu().Equal(cpu))
assert.True(t, rr.Requests.Memory().Equal(memory))
assert.True(t, rr.Limits.Cpu().Equal(cpu))
assert.True(t, rr.Limits.Memory().Equal(memory))
}

func TestCmp(t *testing.T) {
var (
cls1 = ComponentClassInstance{
ComponentClass: ComponentClass{
CPU: resource.MustParse("1"),
Memory: resource.MustParse("1Gi"),
},
}
cls2 = ComponentClassInstance{
ComponentClass: ComponentClass{
CPU: resource.MustParse("2"),
Memory: resource.MustParse("1Gi"),
},
}
cls3 = ComponentClassInstance{
ComponentClass: ComponentClass{
CPU: resource.MustParse("2"),
Memory: resource.MustParse("2Gi"),
},
}
)
assert.True(t, cls1.Cmp(&cls2) < 0)
assert.True(t, cls2.Cmp(&cls3) < 0)
}
140 changes: 112 additions & 28 deletions apis/apps/v1alpha1/componentresourceconstraint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1

import (
"strings"

"golang.org/x/exp/slices"
"gopkg.in/inf.v0"
corev1 "k8s.io/api/core/v1"
Expand All @@ -38,6 +40,10 @@ type ResourceConstraint struct {
// The constraint for memory size.
// +kubebuilder:validation:Required
Memory MemoryConstraint `json:"memory"`

// The constraint for storage size.
// +kubebuilder:validation:Required
Storage StorageConstraint `json:"storage"`
}

type CPUConstraint struct {
Expand Down Expand Up @@ -91,6 +97,18 @@ type MemoryConstraint struct {
MinPerCPU *resource.Quantity `json:"minPerCPU,omitempty"`
}

type StorageConstraint struct {
// The minimum size of storage.
// +kubebuilder:default="20Gi"
// +optional
Min *resource.Quantity `json:"min,omitempty"`

// The maximum size of storage.
// +kubebuilder:default="10Ti"
// +optional
Max *resource.Quantity `json:"max,omitempty"`
}

// +genclient
// +genclient:nonNamespaced
// +k8s:openapi-gen=true
Expand Down Expand Up @@ -118,26 +136,36 @@ func init() {
SchemeBuilder.Register(&ComponentResourceConstraint{}, &ComponentResourceConstraintList{})
}

// ValidateCPU validate if the CPU matches the resource constraints
func (m ResourceConstraint) ValidateCPU(cpu resource.Quantity) bool {
if m.CPU.Min != nil && m.CPU.Min.Cmp(cpu) > 0 {
return false
// ValidateCPU validates if the CPU meets the constraint
func (m *ResourceConstraint) ValidateCPU(cpu *resource.Quantity) bool {
if cpu.IsZero() {
return true
}
if m.CPU.Max != nil && m.CPU.Max.Cmp(cpu) < 0 {
if m.CPU.Min != nil && m.CPU.Min.Cmp(*cpu) > 0 {
return false
}
if m.CPU.Step != nil && inf.NewDec(1, 0).QuoExact(cpu.AsDec(), m.CPU.Step.AsDec()).Scale() != 0 {
if m.CPU.Max != nil && m.CPU.Max.Cmp(*cpu) < 0 {
return false
}
if m.CPU.Slots != nil && slices.Index(m.CPU.Slots, cpu) < 0 {
if m.CPU.Step != nil {
result := inf.NewDec(1, 0).QuoExact(cpu.AsDec(), m.CPU.Step.AsDec())
if result == nil {
return false
}
// the quotient must be an integer
if strings.Contains(strings.TrimRight(result.String(), ".0"), ".") {
return false
}
}
if m.CPU.Slots != nil && slices.Index(m.CPU.Slots, *cpu) < 0 {
return false
}
return true
}

// ValidateMemory validate if the memory matches the resource constraints
func (m ResourceConstraint) ValidateMemory(cpu *resource.Quantity, memory *resource.Quantity) bool {
if memory == nil {
// ValidateMemory validates if the memory meets the constraint
func (m *ResourceConstraint) ValidateMemory(cpu *resource.Quantity, memory *resource.Quantity) bool {
if memory.IsZero() {
return true
}

Expand All @@ -157,51 +185,107 @@ func (m ResourceConstraint) ValidateMemory(cpu *resource.Quantity, memory *resou
return true
}

// ValidateResourceRequirements validate if the resource matches the resource constraints
func (m ResourceConstraint) ValidateResourceRequirements(r *corev1.ResourceRequirements) bool {
var (
cpu = r.Requests.Cpu()
memory = r.Requests.Memory()
)

if cpu.IsZero() && memory.IsZero() {
// ValidateStorage validates if the storage meets the constraint
func (m *ResourceConstraint) ValidateStorage(storage *resource.Quantity) bool {
if storage.IsZero() {
return true
}

if !m.ValidateCPU(*cpu) {
if m.Storage.Min != nil && m.Storage.Min.Cmp(*storage) > 0 {
return false
}
if m.Storage.Max != nil && m.Storage.Max.Cmp(*storage) < 0 {
return false
}
return true
}

// ValidateResources validates if the resources meets the constraint
func (m *ResourceConstraint) ValidateResources(r corev1.ResourceList) bool {
if !m.ValidateCPU(r.Cpu()) {
return false
}

if !m.ValidateMemory(r.Cpu(), r.Memory()) {
return false
}

if !m.ValidateMemory(cpu, memory) {
if !m.ValidateStorage(r.Storage()) {
return false
}

return true
}

// FindMatchingConstraints find all constraints that resource matches
func (c *ComponentResourceConstraint) FindMatchingConstraints(r *corev1.ResourceRequirements) []ResourceConstraint {
func (m *ResourceConstraint) CompleteResources(r corev1.ResourceList) corev1.ResourceList {
if r.Cpu().IsZero() || !r.Memory().IsZero() {
return corev1.ResourceList{corev1.ResourceCPU: *r.Cpu(), corev1.ResourceMemory: *r.Memory()}
}

var memory *inf.Dec
if m.Memory.SizePerCPU != nil {
memory = inf.NewDec(1, 0).Mul(r.Cpu().AsDec(), m.Memory.SizePerCPU.AsDec())
} else {
memory = inf.NewDec(1, 0).Mul(r.Cpu().AsDec(), m.Memory.MinPerCPU.AsDec())
}
return corev1.ResourceList{
corev1.ResourceCPU: *r.Cpu(),
corev1.ResourceMemory: resource.MustParse(memory.String()),
}
}

// GetMinimalResources gets the minimal resources meets the constraint
func (m *ResourceConstraint) GetMinimalResources() corev1.ResourceList {
var (
minCPU resource.Quantity
minMemory resource.Quantity
)

if len(m.CPU.Slots) == 0 && (m.CPU.Min == nil || m.CPU.Min.IsZero()) {
return corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1"),
corev1.ResourceMemory: resource.MustParse("1Gi"),
}
}

if len(m.CPU.Slots) > 0 {
minCPU = m.CPU.Slots[0]
}

if minCPU.IsZero() || (m.CPU.Min != nil && minCPU.Cmp(*m.CPU.Min) > 0) {
minCPU = *m.CPU.Min
}

var memory *inf.Dec
if m.Memory.MinPerCPU != nil {
memory = inf.NewDec(1, 0).Mul(minCPU.AsDec(), m.Memory.MinPerCPU.AsDec())
} else {
memory = inf.NewDec(1, 0).Mul(minCPU.AsDec(), m.Memory.SizePerCPU.AsDec())
}
minMemory = resource.MustParse(memory.String())
return corev1.ResourceList{corev1.ResourceCPU: minCPU, corev1.ResourceMemory: minMemory}
}

// FindMatchingConstraints find all constraints that resource satisfies.
func (c *ComponentResourceConstraint) FindMatchingConstraints(r corev1.ResourceList) []ResourceConstraint {
if c == nil {
return nil
}
var constraints []ResourceConstraint
for _, constraint := range c.Spec.Constraints {
if constraint.ValidateResourceRequirements(r) {
if constraint.ValidateResources(r) {
constraints = append(constraints, constraint)
}
}
return constraints
}

// MatchClass checks if the class meets the constraints
func (c *ComponentResourceConstraint) MatchClass(class *ComponentClassInstance) bool {
request := corev1.ResourceList{
corev1.ResourceCPU: class.CPU,
corev1.ResourceMemory: class.Memory,
}
resource := &corev1.ResourceRequirements{
Limits: request,
Requests: request,
}
constraints := c.FindMatchingConstraints(resource)
constraints := c.FindMatchingConstraints(request)
return len(constraints) > 0
}
10 changes: 4 additions & 6 deletions apis/apps/v1alpha1/componentresourceconstraint_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,11 @@ func TestResourceConstraints(t *testing.T) {
cpu = resource.MustParse(item.cpu)
memory = resource.MustParse(item.memory)
)
requirements := &corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: cpu,
corev1.ResourceMemory: memory,
},
requests := corev1.ResourceList{
corev1.ResourceCPU: cpu,
corev1.ResourceMemory: memory,
}
assert.Equal(t, item.expect, len(cf.FindMatchingConstraints(requirements)) > 0)
assert.Equal(t, item.expect, len(cf.FindMatchingConstraints(requests)) > 0)

class := &ComponentClassInstance{
ComponentClass: ComponentClass{
Expand Down
8 changes: 4 additions & 4 deletions apis/apps/v1alpha1/opsrequest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ type VerticalScaling struct {
// +kubebuilder:pruning:PreserveUnknownFields
corev1.ResourceRequirements `json:",inline"`

// class specifies the class name of the component
// classDefRef reference class defined in ComponentClassDefinition.
// +optional
Class string `json:"class,omitempty"`
ClassDefRef *ClassDefRef `json:"classDefRef,omitempty"`
}

// VolumeExpansion defines the variables of volume expansion operation.
Expand Down Expand Up @@ -369,9 +369,9 @@ type LastComponentConfiguration struct {
// +optional
corev1.ResourceRequirements `json:",inline,omitempty"`

// the last class name of the component.
// classDefRef reference class defined in ComponentClassDefinition.
// +optional
Class string `json:"class,omitempty"`
ClassDefRef *ClassDefRef `json:"classDefRef,omitempty"`

// volumeClaimTemplates records the last volumeClaimTemplates of the component.
// +optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,30 @@ spec:
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
storage:
description: The constraint for storage size.
properties:
max:
anyOf:
- type: integer
- type: string
default: 10Ti
description: The maximum size of storage.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
min:
anyOf:
- type: integer
- type: string
default: 20Gi
description: The minimum size of storage.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
required:
- cpu
- memory
- storage
type: object
type: array
type: object
Expand Down
Loading

0 comments on commit d42964d

Please sign in to comment.