Skip to content

Commit

Permalink
add check before creation and fix test case
Browse files Browse the repository at this point in the history
  • Loading branch information
lynnleelhl committed Apr 12, 2023
2 parents acd5ccf + 380027d commit d8c6a47
Show file tree
Hide file tree
Showing 321 changed files with 9,211 additions and 3,013 deletions.
1 change: 0 additions & 1 deletion .github/utils/get_release_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
githubEnv.write("WITH_RELEASE_NOTES=true\n")
else:
print("{} is not found".format(releaseNotePath))
sys.exit(1)
print("Release build from {} ...".format(gitRef))

githubEnv.write("REL_VERSION={}\n".format(releaseVersion))
17 changes: 7 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,20 @@ cue-fmt: cuetool ## Run cue fmt against code.
git ls-files --exclude-standard | grep "\.cue$$" | xargs $(CUE) fmt
git ls-files --exclude-standard | grep "\.cue$$" | xargs $(CUE) fix

.PHONY: fast-lint
fast-lint: golangci staticcheck vet # [INTERNAL] fast lint
.PHONY: lint-fast
lint-fast: golangci staticcheck vet # [INTERNAL] fast lint
$(GOLANGCILINT) run ./...

.PHONY: lint
lint: test-go-generate generate ## Run golangci-lint against code.
$(MAKE) fast-lint
$(MAKE) lint-fast

.PHONY: staticcheck
staticcheck: staticchecktool ## Run staticcheck against code.
$(STATICCHECK) ./...

.PHONY: build-checks
build-checks: generate fmt vet goimports fast-lint ## Run build checks.
build-checks: generate fmt vet goimports lint-fast ## Run build checks.

.PHONY: mod-download
mod-download: ## Run go mod download against go modules.
Expand Down Expand Up @@ -269,7 +269,6 @@ kbcli-doc: generate ## generate CLI command reference manual.
$(GO) run ./hack/docgen/cli/main.go ./docs/user_docs/cli



##@ Operator Controller Manager

.PHONY: manager
Expand Down Expand Up @@ -301,8 +300,6 @@ ARGUMENTS=
DEBUG_PORT=2345
run-delve: manifests generate fmt vet ## Run Delve debugger.
dlv --listen=:$(DEBUG_PORT) --headless=true --api-version=2 --accept-multiclient debug $(GO_PACKAGE) -- $(ARGUMENTS)


##@ Deployment

ifndef ignore-not-found
Expand All @@ -312,12 +309,10 @@ endif
.PHONY: install
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
($(KUSTOMIZE) build config/crd | kubectl replace -f -) || ($(KUSTOMIZE) build config/crd | kubectl create -f -)
$(KUSTOMIZE) build $(shell $(GO) env GOPATH)/pkg/mod/github.com/kubernetes-csi/external-snapshotter/client/[email protected]/config/crd | kubectl apply -f -

.PHONY: uninstall
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
$(KUSTOMIZE) build $(shell $(GO) env GOPATH)/pkg/mod/github.com/kubernetes-csi/external-snapshotter/client/[email protected]/config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -

.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
Expand Down Expand Up @@ -397,6 +392,8 @@ bump-chart-ver: \
bump-single-chart-ver.milvus \
bump-single-chart-ver.qdrant \
bump-single-chart-ver.qdrant-cluster \
bump-single-chart-ver.weaviate \
bump-single-chart-ver.weaviate-cluster \
bump-single-chart-ver.chatgpt-retrieval-plugin
bump-chart-ver: ## Bump helm chart version.

Expand Down Expand Up @@ -750,7 +747,7 @@ endif
.PHONY: render-smoke-testdata-manifests
render-smoke-testdata-manifests: ## Update E2E test dataset
$(HELM) template mycluster deploy/apecloud-mysql-cluster > test/e2e/testdata/smoketest/wesql/00_wesqlcluster.yaml
$(HELM) template mycluster deploy/postgresqlcluster > test/e2e/testdata/smoketest/postgresql/00_postgresqlcluster.yaml
$(HELM) template mycluster deploy/postgresql-cluster > test/e2e/testdata/smoketest/postgresql/00_postgresqlcluster.yaml
$(HELM) template mycluster deploy/redis > test/e2e/testdata/smoketest/redis/00_rediscluster.yaml
$(HELM) template mycluster deploy/redis-cluster >> test/e2e/testdata/smoketest/redis/00_rediscluster.yaml

Expand Down
10 changes: 8 additions & 2 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,22 @@ resources:
crdVersion: v1
namespaced: true
domain: troubleshoot.sh
group:
kind: HostPreflight
path: github.com/apecloud/kubeblocks/externalapis/preflight/v1beta2
version: v1beta2
- api:
crdVersion: v1
namespaced: true
domain: troubleshoot.sh
group:
kind: Preflight
path: github.com/apecloud/kubeblocks/externalapis/preflight/v1beta2
version: v1beta2
- api:
crdVersion: v1
namespaced: true
domain: kubeblocks.io
group: apps
kind: ClassFamily
path: github.com/apecloud/kubeblocks/apis/apps/v1alpha1
version: v1alpha1
version: "3"
199 changes: 199 additions & 0 deletions apis/apps/v1alpha1/classfamily_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
Copyright ApeCloud, Inc.
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 (
"golang.org/x/exp/slices"
"gopkg.in/inf.v0"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ClassFamilySpec defines the desired state of ClassFamily
type ClassFamilySpec struct {
// Class family models, generally, a model is a specific constraint for CPU, memory and their relation.
Models []ClassFamilyModel `json:"models,omitempty"`
}

type ClassFamilyModel struct {
// The constraint for vcpu cores.
// +kubebuilder:validation:Required
CPU CPUConstraint `json:"cpu,omitempty"`

// The constraint for memory size.
// +kubebuilder:validation:Required
Memory MemoryConstraint `json:"memory,omitempty"`
}

type CPUConstraint struct {
// The maximum count of vcpu cores, [Min, Max] defines a range for valid vcpu cores, and the value in this range
// must be multiple times of Step. It's useful to define a large number of valid values without defining them one by
// one. Please see the documentation for Step for some examples.
// If Slots is specified, Max, Min, and Step are ignored
// +optional
Max *resource.Quantity `json:"max,omitempty"`

// The minimum count of vcpu cores, [Min, Max] defines a range for valid vcpu cores, and the value in this range
// must be multiple times of Step. It's useful to define a large number of valid values without defining them one by
// one. Please see the documentation for Step for some examples.
// If Slots is specified, Max, Min, and Step are ignored
// +optional
Min *resource.Quantity `json:"min,omitempty"`

// The minimum granularity of vcpu cores, [Min, Max] defines a range for valid vcpu cores and the value in this range must be
// multiple times of Step.
// For example:
// 1. Min is 2, Max is 8, Step is 2, and the valid vcpu core is {2, 4, 6, 8}.
// 2. Min is 0.5, Max is 2, Step is 0.5, and the valid vcpu core is {0.5, 1, 1.5, 2}.
// +optional
Step *resource.Quantity `json:"step,omitempty"`

// The valid vcpu cores, it's useful if you want to define valid vcpu cores explicitly.
// If Slots is specified, Max, Min, and Step are ignored
// +optional
Slots []resource.Quantity `json:"slots,omitempty"`
}

type MemoryConstraint struct {
// The size of memory per vcpu core.
// For example: 1Gi, 200Mi.
// If SizePerCPU is specified, MinPerCPU and MaxPerCPU are ignore.
// +optional
SizePerCPU *resource.Quantity `json:"sizePerCPU,omitempty"`

// The maximum size of memory per vcpu core, [MinPerCPU, MaxPerCPU] defines a range for valid memory size per vcpu core.
// It is useful on GCP as the ratio between the CPU and memory may be a range.
// If SizePerCPU is specified, MinPerCPU and MaxPerCPU are ignored.
// Reference: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types
// +optional
MaxPerCPU *resource.Quantity `json:"maxPerCPU,omitempty"`

// The minimum size of memory per vcpu core, [MinPerCPU, MaxPerCPU] defines a range for valid memory size per vcpu core.
// It is useful on GCP as the ratio between the CPU and memory may be a range.
// If SizePerCPU is specified, MinPerCPU and MaxPerCPU are ignored.
// Reference: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types
// +optional
MinPerCPU *resource.Quantity `json:"minPerCPU,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={kubeblocks,all},scope=Cluster,shortName=cf

// ClassFamily is the Schema for the classfamilies API
type ClassFamily struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ClassFamilySpec `json:"spec,omitempty"`
}

// +kubebuilder:object:root=true

// ClassFamilyList contains a list of ClassFamily
type ClassFamilyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ClassFamily `json:"items"`
}

func init() {
SchemeBuilder.Register(&ClassFamily{}, &ClassFamilyList{})
}

// ValidateCPU validate if the CPU matches the class family model constraint
func (m *ClassFamilyModel) ValidateCPU(cpu resource.Quantity) bool {
if m == nil {
return false
}
if m.CPU.Min != nil && m.CPU.Min.Cmp(cpu) > 0 {
return false
}
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 {
return false
}
return true
}

// ValidateMemory validate if the memory matches the class family model constraint
func (m *ClassFamilyModel) ValidateMemory(cpu *resource.Quantity, memory *resource.Quantity) bool {
if m == nil {
return false
}

if memory == nil {
return true
}

// fast path if cpu is specified
if cpu != nil && m.Memory.SizePerCPU != nil {
return inf.NewDec(1, 0).Mul(cpu.AsDec(), m.Memory.SizePerCPU.AsDec()).Cmp(memory.AsDec()) == 0
}

if cpu != nil && m.Memory.MaxPerCPU != nil && m.Memory.MinPerCPU != nil {
maxMemory := inf.NewDec(1, 0).Mul(cpu.AsDec(), m.Memory.MaxPerCPU.AsDec())
minMemory := inf.NewDec(1, 0).Mul(cpu.AsDec(), m.Memory.MinPerCPU.AsDec())
return maxMemory.Cmp(memory.AsDec()) >= 0 && minMemory.Cmp(memory.AsDec()) <= 0
}

// TODO slow path if cpu is not specified

return true
}

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

if m == nil {
return false
}

if cpu.IsZero() && memory.IsZero() {
return true
}

if !m.ValidateCPU(*cpu) {
return false
}

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

return true
}

// FindMatchingModels find all class family models that resource matches
func (c *ClassFamily) FindMatchingModels(r *corev1.ResourceRequirements) []ClassFamilyModel {
if c == nil {
return nil
}
var models []ClassFamilyModel
for _, model := range c.Spec.Models {
if model.ValidateResourceRequirements(r) {
models = append(models, model)
}
}
return models
}
83 changes: 83 additions & 0 deletions apis/apps/v1alpha1/classfamily_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright ApeCloud, Inc.
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"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/yaml"
)

const classFamilyBytes = `
# API scope: cluster
# ClusterClassFamily
apiVersion: "apps.kubeblocks.io/v1alpha1"
kind: "ClassFamily"
metadata:
name: kb-class-family-general
spec:
models:
- cpu:
min: 0.5
max: 128
step: 0.5
memory:
sizePerCPU: 4Gi
- cpu:
slots: [0.1, 0.2, 0.4, 0.6, 0.8, 1]
memory:
minPerCPU: 200Mi
- cpu:
min: 0.1
max: 64
step: 0.1
memory:
minPerCPU: 4Gi
maxPerCPU: 8Gi
`

func TestClassFamily_ValidateResourceRequirements(t *testing.T) {
var cf ClassFamily
err := yaml.Unmarshal([]byte(classFamilyBytes), &cf)
if err != nil {
panic("Failed to unmarshal class family: %v" + err.Error())
}
cases := []struct {
cpu string
memory string
expect bool
}{
{cpu: "0.5", memory: "2Gi", expect: true},
{cpu: "0.2", memory: "40Mi", expect: true},
{cpu: "1", memory: "6Gi", expect: true},
{cpu: "2", memory: "20Gi", expect: false},
{cpu: "2", memory: "6Gi", expect: false},
}

for _, item := range cases {
requirements := &corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse(item.cpu),
corev1.ResourceMemory: resource.MustParse(item.memory),
},
}
assert.Equal(t, item.expect, len(cf.FindMatchingModels(requirements)) > 0)
}
}
Loading

0 comments on commit d8c6a47

Please sign in to comment.