diff --git a/hack/update-generated-conversions.sh b/hack/update-generated-conversions.sh index 11a412b1f..8d2a7a467 100755 --- a/hack/update-generated-conversions.sh +++ b/hack/update-generated-conversions.sh @@ -5,5 +5,5 @@ go build -o "${OS_OUTPUT_BINPATH}/conversion-gen" "k8s.io/code-generator/cmd/con ${OS_OUTPUT_BINPATH}/conversion-gen \ --go-header-file "hack/boilerplate/boilerplate.go.txt" \ - --input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods" \ + --input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods,${PRJ_PREFIX}/pkg/framework/plugins/nodeutilization" \ --output-file-base zz_generated.conversion diff --git a/hack/update-generated-deep-copies.sh b/hack/update-generated-deep-copies.sh index cb7c0c113..49f533152 100755 --- a/hack/update-generated-deep-copies.sh +++ b/hack/update-generated-deep-copies.sh @@ -5,6 +5,6 @@ go build -o "${OS_OUTPUT_BINPATH}/deepcopy-gen" "k8s.io/code-generator/cmd/deepc ${OS_OUTPUT_BINPATH}/deepcopy-gen \ --go-header-file "hack/boilerplate/boilerplate.go.txt" \ - --input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig,${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/defaultevictor/,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods" \ + --input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig,${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/defaultevictor/,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods,${PRJ_PREFIX}/pkg/framework/plugins/nodeutilization" \ --output-file-base zz_generated.deepcopy diff --git a/pkg/apis/componentconfig/types_pluginargs.go b/pkg/apis/componentconfig/types_pluginargs.go index b7a0829ba..2394c1904 100644 --- a/pkg/apis/componentconfig/types_pluginargs.go +++ b/pkg/apis/componentconfig/types_pluginargs.go @@ -97,23 +97,3 @@ type RemovePodsViolatingInterPodAntiAffinityArgs struct { Namespaces *api.Namespaces LabelSelector *metav1.LabelSelector } - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type LowNodeUtilizationArgs struct { - metav1.TypeMeta - - UseDeviationThresholds bool - Thresholds api.ResourceThresholds - TargetThresholds api.ResourceThresholds - NumberOfNodes int -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type HighNodeUtilizationArgs struct { - metav1.TypeMeta - - Thresholds api.ResourceThresholds - NumberOfNodes int -} diff --git a/pkg/apis/componentconfig/validation/validation_pluginargs.go b/pkg/apis/componentconfig/validation/validation_pluginargs.go index a4023abfa..d19a3421c 100644 --- a/pkg/apis/componentconfig/validation/validation_pluginargs.go +++ b/pkg/apis/componentconfig/validation/validation_pluginargs.go @@ -149,47 +149,3 @@ func validatePodLifeTimeStates(states []string) error { return nil } - -func ValidateHighNodeUtilizationArgs(args *componentconfig.HighNodeUtilizationArgs) error { - return validateThresholds(args.Thresholds) -} - -func ValidateLowNodeUtilizationArgs(args *componentconfig.LowNodeUtilizationArgs) error { - return validateLowNodeUtilizationThresholds(args.Thresholds, args.TargetThresholds, args.UseDeviationThresholds) -} - -func validateLowNodeUtilizationThresholds(thresholds, targetThresholds api.ResourceThresholds, useDeviationThresholds bool) error { - // validate thresholds and targetThresholds config - if err := validateThresholds(thresholds); err != nil { - return fmt.Errorf("thresholds config is not valid: %v", err) - } - if err := validateThresholds(targetThresholds); err != nil { - return fmt.Errorf("targetThresholds config is not valid: %v", err) - } - - // validate if thresholds and targetThresholds have same resources configured - if len(thresholds) != len(targetThresholds) { - return fmt.Errorf("thresholds and targetThresholds configured different resources") - } - for resourceName, value := range thresholds { - if targetValue, ok := targetThresholds[resourceName]; !ok { - return fmt.Errorf("thresholds and targetThresholds configured different resources") - } else if value > targetValue && !useDeviationThresholds { - return fmt.Errorf("thresholds' %v percentage is greater than targetThresholds'", resourceName) - } - } - return nil -} - -// validateThresholds checks if thresholds have valid resource name and resource percentage configured -func validateThresholds(thresholds api.ResourceThresholds) error { - if len(thresholds) == 0 { - return fmt.Errorf("no resource threshold is configured") - } - for name, percent := range thresholds { - if percent < MinResourcePercentage || percent > MaxResourcePercentage { - return fmt.Errorf("%v threshold not in [%v, %v] range", name, MinResourcePercentage, MaxResourcePercentage) - } - } - return nil -} diff --git a/pkg/apis/componentconfig/validation/validation_pluginargs_test.go b/pkg/apis/componentconfig/validation/validation_pluginargs_test.go index e78c69ed4..c0ca2ce02 100644 --- a/pkg/apis/componentconfig/validation/validation_pluginargs_test.go +++ b/pkg/apis/componentconfig/validation/validation_pluginargs_test.go @@ -17,7 +17,6 @@ limitations under the License. package validation import ( - "fmt" "testing" v1 "k8s.io/api/core/v1" @@ -169,163 +168,3 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) { }) } } - -func TestValidateLowNodeUtilizationPluginConfig(t *testing.T) { - var extendedResource = v1.ResourceName("example.com/foo") - tests := []struct { - name string - thresholds api.ResourceThresholds - targetThresholds api.ResourceThresholds - errInfo error - }{ - { - name: "passing invalid thresholds", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 120, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - }, - errInfo: fmt.Errorf("thresholds config is not valid: %v", fmt.Errorf( - "%v threshold not in [%v, %v] range", v1.ResourceMemory, MinResourcePercentage, MaxResourcePercentage)), - }, - { - name: "thresholds and targetThresholds configured different num of resources", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - v1.ResourcePods: 80, - }, - errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), - }, - { - name: "thresholds and targetThresholds configured different resources", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourcePods: 80, - }, - errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), - }, - { - name: "thresholds' CPU config value is greater than targetThresholds'", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 90, - v1.ResourceMemory: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - }, - errInfo: fmt.Errorf("thresholds' %v percentage is greater than targetThresholds'", v1.ResourceCPU), - }, - { - name: "only thresholds configured extended resource", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - extendedResource: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - }, - errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), - }, - { - name: "only targetThresholds configured extended resource", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - extendedResource: 80, - }, - errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), - }, - { - name: "thresholds and targetThresholds configured different extended resources", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - extendedResource: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - "example.com/bar": 80, - }, - errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), - }, - { - name: "thresholds' extended resource config value is greater than targetThresholds'", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - extendedResource: 90, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - extendedResource: 20, - }, - errInfo: fmt.Errorf("thresholds' %v percentage is greater than targetThresholds'", extendedResource), - }, - { - name: "passing valid plugin config", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - }, - errInfo: nil, - }, - { - name: "passing valid plugin config with extended resource", - thresholds: api.ResourceThresholds{ - v1.ResourceCPU: 20, - v1.ResourceMemory: 20, - extendedResource: 20, - }, - targetThresholds: api.ResourceThresholds{ - v1.ResourceCPU: 80, - v1.ResourceMemory: 80, - extendedResource: 80, - }, - errInfo: nil, - }, - } - - for _, testCase := range tests { - args := &componentconfig.LowNodeUtilizationArgs{ - - Thresholds: testCase.thresholds, - TargetThresholds: testCase.targetThresholds, - } - validateErr := validateLowNodeUtilizationThresholds(args.Thresholds, args.TargetThresholds, false) - - if validateErr == nil || testCase.errInfo == nil { - if validateErr != testCase.errInfo { - t.Errorf("expected validity of plugin config: thresholds %#v targetThresholds %#v to be %v but got %v instead", - testCase.thresholds, testCase.targetThresholds, testCase.errInfo, validateErr) - } - } else if validateErr.Error() != testCase.errInfo.Error() { - t.Errorf("expected validity of plugin config: thresholds %#v targetThresholds %#v to be %v but got %v instead", - testCase.thresholds, testCase.targetThresholds, testCase.errInfo, validateErr) - } - } -} diff --git a/pkg/apis/componentconfig/zz_generated.deepcopy.go b/pkg/apis/componentconfig/zz_generated.deepcopy.go index 6838d2f6f..635b98081 100644 --- a/pkg/apis/componentconfig/zz_generated.deepcopy.go +++ b/pkg/apis/componentconfig/zz_generated.deepcopy.go @@ -54,77 +54,6 @@ func (in *DeschedulerConfiguration) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HighNodeUtilizationArgs) DeepCopyInto(out *HighNodeUtilizationArgs) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.Thresholds != nil { - in, out := &in.Thresholds, &out.Thresholds - *out = make(api.ResourceThresholds, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HighNodeUtilizationArgs. -func (in *HighNodeUtilizationArgs) DeepCopy() *HighNodeUtilizationArgs { - if in == nil { - return nil - } - out := new(HighNodeUtilizationArgs) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HighNodeUtilizationArgs) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LowNodeUtilizationArgs) DeepCopyInto(out *LowNodeUtilizationArgs) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.Thresholds != nil { - in, out := &in.Thresholds, &out.Thresholds - *out = make(api.ResourceThresholds, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.TargetThresholds != nil { - in, out := &in.TargetThresholds, &out.TargetThresholds - *out = make(api.ResourceThresholds, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LowNodeUtilizationArgs. -func (in *LowNodeUtilizationArgs) DeepCopy() *LowNodeUtilizationArgs { - if in == nil { - return nil - } - out := new(LowNodeUtilizationArgs) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *LowNodeUtilizationArgs) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) { *out = *in diff --git a/pkg/descheduler/strategy_migration.go b/pkg/descheduler/strategy_migration.go index d4255e4e8..ae08e46f5 100644 --- a/pkg/descheduler/strategy_migration.go +++ b/pkg/descheduler/strategy_migration.go @@ -229,12 +229,12 @@ var pluginsMap = map[string]func(ctx context.Context, nodes []*v1.Node, params * } }, "HighNodeUtilization": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { - args := &componentconfig.HighNodeUtilizationArgs{ + args := &nodeutilization.HighNodeUtilizationArgs{ Thresholds: params.NodeResourceUtilizationThresholds.Thresholds, NumberOfNodes: params.NodeResourceUtilizationThresholds.NumberOfNodes, } - if err := validation.ValidateHighNodeUtilizationArgs(args); err != nil { + if err := nodeutilization.ValidateHighNodeUtilizationArgs(args); err != nil { klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", nodeutilization.HighNodeUtilizationPluginName) return } @@ -249,14 +249,14 @@ var pluginsMap = map[string]func(ctx context.Context, nodes []*v1.Node, params * } }, "LowNodeUtilization": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { - args := &componentconfig.LowNodeUtilizationArgs{ + args := &nodeutilization.LowNodeUtilizationArgs{ Thresholds: params.NodeResourceUtilizationThresholds.Thresholds, TargetThresholds: params.NodeResourceUtilizationThresholds.TargetThresholds, UseDeviationThresholds: params.NodeResourceUtilizationThresholds.UseDeviationThresholds, NumberOfNodes: params.NodeResourceUtilizationThresholds.NumberOfNodes, } - if err := validation.ValidateLowNodeUtilizationArgs(args); err != nil { + if err := nodeutilization.ValidateLowNodeUtilizationArgs(args); err != nil { klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", nodeutilization.LowNodeUtilizationPluginName) return } diff --git a/pkg/framework/plugins/nodeutilization/highnodeutilization.go b/pkg/framework/plugins/nodeutilization/highnodeutilization.go index 1eb47254c..0c3977153 100644 --- a/pkg/framework/plugins/nodeutilization/highnodeutilization.go +++ b/pkg/framework/plugins/nodeutilization/highnodeutilization.go @@ -27,7 +27,6 @@ import ( "sigs.k8s.io/descheduler/pkg/api" nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node" - "sigs.k8s.io/descheduler/pkg/apis/componentconfig" podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" "sigs.k8s.io/descheduler/pkg/framework" ) @@ -39,7 +38,7 @@ const HighNodeUtilizationPluginName = "HighNodeUtilization" type HighNodeUtilization struct { handle framework.Handle - args *componentconfig.HighNodeUtilizationArgs + args *HighNodeUtilizationArgs podFilter func(pod *v1.Pod) bool } @@ -47,7 +46,7 @@ var _ framework.BalancePlugin = &HighNodeUtilization{} // NewHighNodeUtilization builds plugin from its arguments while passing a handle func NewHighNodeUtilization(args runtime.Object, handle framework.Handle) (framework.Plugin, error) { - highNodeUtilizatioArgs, ok := args.(*componentconfig.HighNodeUtilizationArgs) + highNodeUtilizatioArgs, ok := args.(*HighNodeUtilizationArgs) if !ok { return nil, fmt.Errorf("want args to be of type HighNodeUtilizationArgs, got %T", args) } diff --git a/pkg/framework/plugins/nodeutilization/highnodeutilization_test.go b/pkg/framework/plugins/nodeutilization/highnodeutilization_test.go index 1ba9b9e22..fe923d29c 100644 --- a/pkg/framework/plugins/nodeutilization/highnodeutilization_test.go +++ b/pkg/framework/plugins/nodeutilization/highnodeutilization_test.go @@ -31,7 +31,6 @@ import ( "k8s.io/client-go/tools/events" "sigs.k8s.io/descheduler/pkg/api" - "sigs.k8s.io/descheduler/pkg/apis/componentconfig" "sigs.k8s.io/descheduler/pkg/descheduler/evictions" podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" "sigs.k8s.io/descheduler/pkg/framework" @@ -524,7 +523,7 @@ func TestHighNodeUtilization(t *testing.T) { SharedInformerFactoryImpl: sharedInformerFactory, } - plugin, err := NewHighNodeUtilization(&componentconfig.HighNodeUtilizationArgs{ + plugin, err := NewHighNodeUtilization(&HighNodeUtilizationArgs{ Thresholds: testCase.thresholds, }, handle) @@ -676,7 +675,7 @@ func TestHighNodeUtilizationWithTaints(t *testing.T) { SharedInformerFactoryImpl: sharedInformerFactory, } - plugin, err := NewHighNodeUtilization(&componentconfig.HighNodeUtilizationArgs{ + plugin, err := NewHighNodeUtilization(&HighNodeUtilizationArgs{ Thresholds: api.ResourceThresholds{ v1.ResourceCPU: 40, }, diff --git a/pkg/framework/plugins/nodeutilization/lownodeutilization.go b/pkg/framework/plugins/nodeutilization/lownodeutilization.go index cc3ff2bff..b11ff4dce 100644 --- a/pkg/framework/plugins/nodeutilization/lownodeutilization.go +++ b/pkg/framework/plugins/nodeutilization/lownodeutilization.go @@ -24,7 +24,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" - "sigs.k8s.io/descheduler/pkg/apis/componentconfig" nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node" podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" "sigs.k8s.io/descheduler/pkg/framework" @@ -37,7 +36,7 @@ const LowNodeUtilizationPluginName = "LowNodeUtilization" type LowNodeUtilization struct { handle framework.Handle - args *componentconfig.LowNodeUtilizationArgs + args *LowNodeUtilizationArgs podFilter func(pod *v1.Pod) bool } @@ -45,7 +44,7 @@ var _ framework.BalancePlugin = &LowNodeUtilization{} // NewLowNodeUtilization builds plugin from its arguments while passing a handle func NewLowNodeUtilization(args runtime.Object, handle framework.Handle) (framework.Plugin, error) { - lowNodeUtilizationArgsArgs, ok := args.(*componentconfig.LowNodeUtilizationArgs) + lowNodeUtilizationArgsArgs, ok := args.(*LowNodeUtilizationArgs) if !ok { return nil, fmt.Errorf("want args to be of type LowNodeUtilizationArgs, got %T", args) } diff --git a/pkg/framework/plugins/nodeutilization/lownodeutilization_test.go b/pkg/framework/plugins/nodeutilization/lownodeutilization_test.go index eccffefb6..d7023ff3d 100644 --- a/pkg/framework/plugins/nodeutilization/lownodeutilization_test.go +++ b/pkg/framework/plugins/nodeutilization/lownodeutilization_test.go @@ -22,7 +22,6 @@ import ( "testing" "sigs.k8s.io/descheduler/pkg/api" - "sigs.k8s.io/descheduler/pkg/apis/componentconfig" "sigs.k8s.io/descheduler/pkg/framework" frameworkfake "sigs.k8s.io/descheduler/pkg/framework/fake" "sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor" @@ -790,7 +789,7 @@ func TestLowNodeUtilization(t *testing.T) { SharedInformerFactoryImpl: sharedInformerFactory, } - plugin, err := NewLowNodeUtilization(&componentconfig.LowNodeUtilizationArgs{ + plugin, err := NewLowNodeUtilization(&LowNodeUtilizationArgs{ Thresholds: test.thresholds, TargetThresholds: test.targetThresholds, @@ -963,7 +962,7 @@ func TestLowNodeUtilizationWithTaints(t *testing.T) { SharedInformerFactoryImpl: sharedInformerFactory, } - plugin, err := NewLowNodeUtilization(&componentconfig.LowNodeUtilizationArgs{ + plugin, err := NewLowNodeUtilization(&LowNodeUtilizationArgs{ Thresholds: api.ResourceThresholds{ v1.ResourcePods: 20, diff --git a/pkg/framework/plugins/nodeutilization/types.go b/pkg/framework/plugins/nodeutilization/types.go new file mode 100644 index 000000000..f850f6470 --- /dev/null +++ b/pkg/framework/plugins/nodeutilization/types.go @@ -0,0 +1,41 @@ +/* +Copyright 2022 The Kubernetes Authors. +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 nodeutilization + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/descheduler/pkg/api" +) + +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type LowNodeUtilizationArgs struct { + metav1.TypeMeta + + UseDeviationThresholds bool + Thresholds api.ResourceThresholds + TargetThresholds api.ResourceThresholds + NumberOfNodes int +} + +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type HighNodeUtilizationArgs struct { + metav1.TypeMeta + + Thresholds api.ResourceThresholds + NumberOfNodes int +} diff --git a/pkg/framework/plugins/nodeutilization/validation.go b/pkg/framework/plugins/nodeutilization/validation.go new file mode 100644 index 000000000..89969a8d9 --- /dev/null +++ b/pkg/framework/plugins/nodeutilization/validation.go @@ -0,0 +1,63 @@ +/* +Copyright 2022 The Kubernetes Authors. +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 nodeutilization + +import ( + "fmt" + "sigs.k8s.io/descheduler/pkg/api" +) + +func ValidateHighNodeUtilizationArgs(args *HighNodeUtilizationArgs) error { + return validateThresholds(args.Thresholds) +} + +func ValidateLowNodeUtilizationArgs(args *LowNodeUtilizationArgs) error { + return validateLowNodeUtilizationThresholds(args.Thresholds, args.TargetThresholds, args.UseDeviationThresholds) +} + +func validateLowNodeUtilizationThresholds(thresholds, targetThresholds api.ResourceThresholds, useDeviationThresholds bool) error { + // validate thresholds and targetThresholds config + if err := validateThresholds(thresholds); err != nil { + return fmt.Errorf("thresholds config is not valid: %v", err) + } + if err := validateThresholds(targetThresholds); err != nil { + return fmt.Errorf("targetThresholds config is not valid: %v", err) + } + + // validate if thresholds and targetThresholds have same resources configured + if len(thresholds) != len(targetThresholds) { + return fmt.Errorf("thresholds and targetThresholds configured different resources") + } + for resourceName, value := range thresholds { + if targetValue, ok := targetThresholds[resourceName]; !ok { + return fmt.Errorf("thresholds and targetThresholds configured different resources") + } else if value > targetValue && !useDeviationThresholds { + return fmt.Errorf("thresholds' %v percentage is greater than targetThresholds'", resourceName) + } + } + return nil +} + +// validateThresholds checks if thresholds have valid resource name and resource percentage configured +func validateThresholds(thresholds api.ResourceThresholds) error { + if len(thresholds) == 0 { + return fmt.Errorf("no resource threshold is configured") + } + for name, percent := range thresholds { + if percent < MinResourcePercentage || percent > MaxResourcePercentage { + return fmt.Errorf("%v threshold not in [%v, %v] range", name, MinResourcePercentage, MaxResourcePercentage) + } + } + return nil +} diff --git a/pkg/framework/plugins/nodeutilization/validation_test.go b/pkg/framework/plugins/nodeutilization/validation_test.go new file mode 100644 index 000000000..655c19cad --- /dev/null +++ b/pkg/framework/plugins/nodeutilization/validation_test.go @@ -0,0 +1,184 @@ +/* +Copyright 2022 The Kubernetes Authors. + +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 nodeutilization + +import ( + "fmt" + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/descheduler/pkg/api" + "testing" +) + +func TestValidateLowNodeUtilizationPluginConfig(t *testing.T) { + var extendedResource = v1.ResourceName("example.com/foo") + tests := []struct { + name string + thresholds api.ResourceThresholds + targetThresholds api.ResourceThresholds + errInfo error + }{ + { + name: "passing invalid thresholds", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 120, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + }, + errInfo: fmt.Errorf("thresholds config is not valid: %v", fmt.Errorf( + "%v threshold not in [%v, %v] range", v1.ResourceMemory, MinResourcePercentage, MaxResourcePercentage)), + }, + { + name: "thresholds and targetThresholds configured different num of resources", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + v1.ResourcePods: 80, + }, + errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), + }, + { + name: "thresholds and targetThresholds configured different resources", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourcePods: 80, + }, + errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), + }, + { + name: "thresholds' CPU config value is greater than targetThresholds'", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 90, + v1.ResourceMemory: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + }, + errInfo: fmt.Errorf("thresholds' %v percentage is greater than targetThresholds'", v1.ResourceCPU), + }, + { + name: "only thresholds configured extended resource", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + extendedResource: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + }, + errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), + }, + { + name: "only targetThresholds configured extended resource", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + extendedResource: 80, + }, + errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), + }, + { + name: "thresholds and targetThresholds configured different extended resources", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + extendedResource: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + "example.com/bar": 80, + }, + errInfo: fmt.Errorf("thresholds and targetThresholds configured different resources"), + }, + { + name: "thresholds' extended resource config value is greater than targetThresholds'", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + extendedResource: 90, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + extendedResource: 20, + }, + errInfo: fmt.Errorf("thresholds' %v percentage is greater than targetThresholds'", extendedResource), + }, + { + name: "passing valid plugin config", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + }, + errInfo: nil, + }, + { + name: "passing valid plugin config with extended resource", + thresholds: api.ResourceThresholds{ + v1.ResourceCPU: 20, + v1.ResourceMemory: 20, + extendedResource: 20, + }, + targetThresholds: api.ResourceThresholds{ + v1.ResourceCPU: 80, + v1.ResourceMemory: 80, + extendedResource: 80, + }, + errInfo: nil, + }, + } + + for _, testCase := range tests { + args := &LowNodeUtilizationArgs{ + + Thresholds: testCase.thresholds, + TargetThresholds: testCase.targetThresholds, + } + validateErr := validateLowNodeUtilizationThresholds(args.Thresholds, args.TargetThresholds, false) + + if validateErr == nil || testCase.errInfo == nil { + if validateErr != testCase.errInfo { + t.Errorf("expected validity of plugin config: thresholds %#v targetThresholds %#v to be %v but got %v instead", + testCase.thresholds, testCase.targetThresholds, testCase.errInfo, validateErr) + } + } else if validateErr.Error() != testCase.errInfo.Error() { + t.Errorf("expected validity of plugin config: thresholds %#v targetThresholds %#v to be %v but got %v instead", + testCase.thresholds, testCase.targetThresholds, testCase.errInfo, validateErr) + } + } +} diff --git a/pkg/framework/plugins/nodeutilization/zz_generated.deepcopy.go b/pkg/framework/plugins/nodeutilization/zz_generated.deepcopy.go new file mode 100644 index 000000000..faab6a47c --- /dev/null +++ b/pkg/framework/plugins/nodeutilization/zz_generated.deepcopy.go @@ -0,0 +1,98 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2022 The Kubernetes Authors. + +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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package nodeutilization + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + api "sigs.k8s.io/descheduler/pkg/api" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HighNodeUtilizationArgs) DeepCopyInto(out *HighNodeUtilizationArgs) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Thresholds != nil { + in, out := &in.Thresholds, &out.Thresholds + *out = make(api.ResourceThresholds, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HighNodeUtilizationArgs. +func (in *HighNodeUtilizationArgs) DeepCopy() *HighNodeUtilizationArgs { + if in == nil { + return nil + } + out := new(HighNodeUtilizationArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HighNodeUtilizationArgs) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LowNodeUtilizationArgs) DeepCopyInto(out *LowNodeUtilizationArgs) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Thresholds != nil { + in, out := &in.Thresholds, &out.Thresholds + *out = make(api.ResourceThresholds, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.TargetThresholds != nil { + in, out := &in.TargetThresholds, &out.TargetThresholds + *out = make(api.ResourceThresholds, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LowNodeUtilizationArgs. +func (in *LowNodeUtilizationArgs) DeepCopy() *LowNodeUtilizationArgs { + if in == nil { + return nil + } + out := new(LowNodeUtilizationArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LowNodeUtilizationArgs) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +}