diff --git a/README.md b/README.md index ac2b2c656..1552b9d10 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ actual usage metrics. Implementing metrics-based descheduling is currently TODO |`thresholdPriority`|int (see [priority filtering](#priority-filtering))| |`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))| |`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))| -|`evictableNamespaces`|(see [namespace filtering](#namespace-filtering))| +|`Namespaces`|(see [namespace filtering](#namespace-filtering))| **Example:** @@ -316,7 +316,7 @@ actual usage metrics. Implementing metrics-based descheduling is currently TODO |`thresholdPriority`|int (see [priority filtering](#priority-filtering))| |`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))| |`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))| -|`evictableNamespaces`|(see [namespace filtering](#namespace-filtering))| +|`Namespaces`|(see [namespace filtering](#namespace-filtering))| **Example:** @@ -615,7 +615,7 @@ The following strategies accept a `namespaces` parameter which allows to specify * `RemoveDuplicates` * `RemovePodsViolatingTopologySpreadConstraint` * `RemoveFailedPods` -* `LowNodeUtilization` and `HighNodeUtilization` (Only filtered right before eviction, parameter named `evictableNamespaces`) +* `LowNodeUtilization` and `HighNodeUtilization` (Only filtered right before eviction) For example: diff --git a/pkg/api/sort.go b/pkg/api/sort.go new file mode 100644 index 000000000..6d2a39d4b --- /dev/null +++ b/pkg/api/sort.go @@ -0,0 +1,23 @@ +/* +Copyright 2017 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 api + +import "sort" + +func SortProfilesByName(profiles []Profile) []Profile { + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Name < profiles[j].Name + }) + return profiles +} diff --git a/pkg/api/types.go b/pkg/api/types.go index d9a3fff0e..f5f4e9a78 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -19,6 +19,7 @@ package api import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -26,24 +27,12 @@ import ( type DeschedulerPolicy struct { metav1.TypeMeta - // Strategies - Strategies StrategyList + // Profiles + Profiles []Profile // NodeSelector for a set of nodes to operate over NodeSelector *string - // EvictFailedBarePods allows pods without ownerReferences and in failed phase to be evicted. - EvictFailedBarePods *bool - - // EvictLocalStoragePods allows pods using local storage to be evicted. - EvictLocalStoragePods *bool - - // EvictSystemCriticalPods allows eviction of pods of any priority (including Kubernetes system pods) - EvictSystemCriticalPods *bool - - // IgnorePVCPods prevents pods with PVCs from being evicted. - IgnorePVCPods *bool - // MaxNoOfPodsToEvictPerNode restricts maximum of pods to be evicted per node. MaxNoOfPodsToEvictPerNode *uint @@ -51,22 +40,6 @@ type DeschedulerPolicy struct { MaxNoOfPodsToEvictPerNamespace *uint } -type ( - StrategyName string - StrategyList map[StrategyName]DeschedulerStrategy -) - -type DeschedulerStrategy struct { - // Enabled or disabled - Enabled bool - - // Weight - Weight int - - // Strategy parameters - Params *StrategyParameters -} - // Namespaces carries a list of included/excluded namespaces // for which a given strategy is applicable type Namespaces struct { @@ -74,64 +47,38 @@ type Namespaces struct { Exclude []string } -// Besides Namespaces only one of its members may be specified -// TODO(jchaloup): move Namespaces ThresholdPriority and ThresholdPriorityClassName to individual strategies -// -// once the policy version is bumped to v1alpha2 -type StrategyParameters struct { - NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds - NodeAffinityType []string - PodsHavingTooManyRestarts *PodsHavingTooManyRestarts - PodLifeTime *PodLifeTime - RemoveDuplicates *RemoveDuplicates - FailedPods *FailedPods - IncludeSoftConstraints bool - Namespaces *Namespaces - ThresholdPriority *int32 - ThresholdPriorityClassName string - LabelSelector *metav1.LabelSelector - NodeFit bool - IncludePreferNoSchedule bool - ExcludedTaints []string -} - type ( Percentage float64 ResourceThresholds map[v1.ResourceName]Percentage ) -type NodeResourceUtilizationThresholds struct { - UseDeviationThresholds bool - Thresholds ResourceThresholds - TargetThresholds ResourceThresholds - NumberOfNodes int -} - -type PodsHavingTooManyRestarts struct { - PodRestartThreshold int32 - IncludingInitContainers bool -} - -type RemoveDuplicates struct { - ExcludeOwnerKinds []string -} - -type PodLifeTime struct { - MaxPodLifeTimeSeconds *uint - States []string - - // Deprecated: Use States instead. - PodStatusPhases []string -} - -type FailedPods struct { - ExcludeOwnerKinds []string - MinPodLifetimeSeconds *uint - Reasons []string - IncludingInitContainers bool -} - type PriorityThreshold struct { Value *int32 Name string } + +type Profile struct { + Name string + PluginConfigs []PluginConfig + Plugins Plugins +} + +type PluginConfig struct { + Name string + Args runtime.Object +} + +type Plugins struct { + PreSort PluginSet + Sort PluginSet + Deschedule PluginSet + Balance PluginSet + Evict PluginSet + Filter PluginSet + PreEvictionFilter PluginSet +} + +type PluginSet struct { + Enabled []string + Disabled []string +} diff --git a/pkg/api/v1alpha1/doc.go b/pkg/api/v1alpha1/doc.go index b1664026e..04a83aeb8 100644 --- a/pkg/api/v1alpha1/doc.go +++ b/pkg/api/v1alpha1/doc.go @@ -15,7 +15,6 @@ limitations under the License. */ // +k8s:deepcopy-gen=package,register -// +k8s:conversion-gen=sigs.k8s.io/descheduler/pkg/api // +k8s:defaulter-gen=TypeMeta // Package v1alpha1 is the v1alpha1 version of the descheduler API diff --git a/pkg/api/v1alpha1/types.go b/pkg/api/v1alpha1/types.go index 9658bd4a8..f70b90068 100644 --- a/pkg/api/v1alpha1/types.go +++ b/pkg/api/v1alpha1/types.go @@ -45,10 +45,10 @@ type DeschedulerPolicy struct { IgnorePVCPods *bool `json:"ignorePvcPods,omitempty"` // MaxNoOfPodsToEvictPerNode restricts maximum of pods to be evicted per node. - MaxNoOfPodsToEvictPerNode *int `json:"maxNoOfPodsToEvictPerNode,omitempty"` + MaxNoOfPodsToEvictPerNode *uint `json:"maxNoOfPodsToEvictPerNode,omitempty"` // MaxNoOfPodsToEvictPerNamespace restricts maximum of pods to be evicted per namespace. - MaxNoOfPodsToEvictPerNamespace *int `json:"maxNoOfPodsToEvictPerNamespace,omitempty"` + MaxNoOfPodsToEvictPerNamespace *uint `json:"maxNoOfPodsToEvictPerNamespace,omitempty"` } type ( diff --git a/pkg/api/v1alpha1/zz_generated.conversion.go b/pkg/api/v1alpha1/zz_generated.conversion.go deleted file mode 100644 index 7bc58de58..000000000 --- a/pkg/api/v1alpha1/zz_generated.conversion.go +++ /dev/null @@ -1,399 +0,0 @@ -//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 conversion-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - unsafe "unsafe" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - api "sigs.k8s.io/descheduler/pkg/api" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*DeschedulerPolicy)(nil), (*api.DeschedulerPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(a.(*DeschedulerPolicy), b.(*api.DeschedulerPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.DeschedulerPolicy)(nil), (*DeschedulerPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(a.(*api.DeschedulerPolicy), b.(*DeschedulerPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*DeschedulerStrategy)(nil), (*api.DeschedulerStrategy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(a.(*DeschedulerStrategy), b.(*api.DeschedulerStrategy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.DeschedulerStrategy)(nil), (*DeschedulerStrategy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(a.(*api.DeschedulerStrategy), b.(*DeschedulerStrategy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*FailedPods)(nil), (*api.FailedPods)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_FailedPods_To_api_FailedPods(a.(*FailedPods), b.(*api.FailedPods), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.FailedPods)(nil), (*FailedPods)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_FailedPods_To_v1alpha1_FailedPods(a.(*api.FailedPods), b.(*FailedPods), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Namespaces)(nil), (*api.Namespaces)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_Namespaces_To_api_Namespaces(a.(*Namespaces), b.(*api.Namespaces), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.Namespaces)(nil), (*Namespaces)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_Namespaces_To_v1alpha1_Namespaces(a.(*api.Namespaces), b.(*Namespaces), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*NodeResourceUtilizationThresholds)(nil), (*api.NodeResourceUtilizationThresholds)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(a.(*NodeResourceUtilizationThresholds), b.(*api.NodeResourceUtilizationThresholds), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.NodeResourceUtilizationThresholds)(nil), (*NodeResourceUtilizationThresholds)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(a.(*api.NodeResourceUtilizationThresholds), b.(*NodeResourceUtilizationThresholds), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*PodLifeTime)(nil), (*api.PodLifeTime)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime(a.(*PodLifeTime), b.(*api.PodLifeTime), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.PodLifeTime)(nil), (*PodLifeTime)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime(a.(*api.PodLifeTime), b.(*PodLifeTime), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*PodsHavingTooManyRestarts)(nil), (*api.PodsHavingTooManyRestarts)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(a.(*PodsHavingTooManyRestarts), b.(*api.PodsHavingTooManyRestarts), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.PodsHavingTooManyRestarts)(nil), (*PodsHavingTooManyRestarts)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(a.(*api.PodsHavingTooManyRestarts), b.(*PodsHavingTooManyRestarts), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*RemoveDuplicates)(nil), (*api.RemoveDuplicates)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(a.(*RemoveDuplicates), b.(*api.RemoveDuplicates), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.RemoveDuplicates)(nil), (*RemoveDuplicates)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(a.(*api.RemoveDuplicates), b.(*RemoveDuplicates), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*StrategyParameters)(nil), (*api.StrategyParameters)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters(a.(*StrategyParameters), b.(*api.StrategyParameters), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*api.StrategyParameters)(nil), (*StrategyParameters)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_api_StrategyParameters_To_v1alpha1_StrategyParameters(a.(*api.StrategyParameters), b.(*StrategyParameters), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *DeschedulerPolicy, out *api.DeschedulerPolicy, s conversion.Scope) error { - out.Strategies = *(*api.StrategyList)(unsafe.Pointer(&in.Strategies)) - out.NodeSelector = (*string)(unsafe.Pointer(in.NodeSelector)) - out.EvictFailedBarePods = (*bool)(unsafe.Pointer(in.EvictFailedBarePods)) - out.EvictLocalStoragePods = (*bool)(unsafe.Pointer(in.EvictLocalStoragePods)) - out.EvictSystemCriticalPods = (*bool)(unsafe.Pointer(in.EvictSystemCriticalPods)) - out.IgnorePVCPods = (*bool)(unsafe.Pointer(in.IgnorePVCPods)) - if in.MaxNoOfPodsToEvictPerNode != nil { - in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode - *out = new(uint) - **out = uint(**in) - } else { - out.MaxNoOfPodsToEvictPerNode = nil - } - if in.MaxNoOfPodsToEvictPerNamespace != nil { - in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace - *out = new(uint) - **out = uint(**in) - } else { - out.MaxNoOfPodsToEvictPerNamespace = nil - } - return nil -} - -// Convert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy is an autogenerated conversion function. -func Convert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *DeschedulerPolicy, out *api.DeschedulerPolicy, s conversion.Scope) error { - return autoConvert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in, out, s) -} - -func autoConvert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in *api.DeschedulerPolicy, out *DeschedulerPolicy, s conversion.Scope) error { - out.Strategies = *(*StrategyList)(unsafe.Pointer(&in.Strategies)) - out.NodeSelector = (*string)(unsafe.Pointer(in.NodeSelector)) - out.EvictFailedBarePods = (*bool)(unsafe.Pointer(in.EvictFailedBarePods)) - out.EvictLocalStoragePods = (*bool)(unsafe.Pointer(in.EvictLocalStoragePods)) - out.EvictSystemCriticalPods = (*bool)(unsafe.Pointer(in.EvictSystemCriticalPods)) - out.IgnorePVCPods = (*bool)(unsafe.Pointer(in.IgnorePVCPods)) - if in.MaxNoOfPodsToEvictPerNode != nil { - in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode - *out = new(int) - **out = int(**in) - } else { - out.MaxNoOfPodsToEvictPerNode = nil - } - if in.MaxNoOfPodsToEvictPerNamespace != nil { - in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace - *out = new(int) - **out = int(**in) - } else { - out.MaxNoOfPodsToEvictPerNamespace = nil - } - return nil -} - -// Convert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy is an autogenerated conversion function. -func Convert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in *api.DeschedulerPolicy, out *DeschedulerPolicy, s conversion.Scope) error { - return autoConvert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in, out, s) -} - -func autoConvert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(in *DeschedulerStrategy, out *api.DeschedulerStrategy, s conversion.Scope) error { - out.Enabled = in.Enabled - out.Weight = in.Weight - out.Params = (*api.StrategyParameters)(unsafe.Pointer(in.Params)) - return nil -} - -// Convert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy is an autogenerated conversion function. -func Convert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(in *DeschedulerStrategy, out *api.DeschedulerStrategy, s conversion.Scope) error { - return autoConvert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(in, out, s) -} - -func autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in *api.DeschedulerStrategy, out *DeschedulerStrategy, s conversion.Scope) error { - out.Enabled = in.Enabled - out.Weight = in.Weight - out.Params = (*StrategyParameters)(unsafe.Pointer(in.Params)) - return nil -} - -// Convert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy is an autogenerated conversion function. -func Convert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in *api.DeschedulerStrategy, out *DeschedulerStrategy, s conversion.Scope) error { - return autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in, out, s) -} - -func autoConvert_v1alpha1_FailedPods_To_api_FailedPods(in *FailedPods, out *api.FailedPods, s conversion.Scope) error { - out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds)) - out.MinPodLifetimeSeconds = (*uint)(unsafe.Pointer(in.MinPodLifetimeSeconds)) - out.Reasons = *(*[]string)(unsafe.Pointer(&in.Reasons)) - out.IncludingInitContainers = in.IncludingInitContainers - return nil -} - -// Convert_v1alpha1_FailedPods_To_api_FailedPods is an autogenerated conversion function. -func Convert_v1alpha1_FailedPods_To_api_FailedPods(in *FailedPods, out *api.FailedPods, s conversion.Scope) error { - return autoConvert_v1alpha1_FailedPods_To_api_FailedPods(in, out, s) -} - -func autoConvert_api_FailedPods_To_v1alpha1_FailedPods(in *api.FailedPods, out *FailedPods, s conversion.Scope) error { - out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds)) - out.MinPodLifetimeSeconds = (*uint)(unsafe.Pointer(in.MinPodLifetimeSeconds)) - out.Reasons = *(*[]string)(unsafe.Pointer(&in.Reasons)) - out.IncludingInitContainers = in.IncludingInitContainers - return nil -} - -// Convert_api_FailedPods_To_v1alpha1_FailedPods is an autogenerated conversion function. -func Convert_api_FailedPods_To_v1alpha1_FailedPods(in *api.FailedPods, out *FailedPods, s conversion.Scope) error { - return autoConvert_api_FailedPods_To_v1alpha1_FailedPods(in, out, s) -} - -func autoConvert_v1alpha1_Namespaces_To_api_Namespaces(in *Namespaces, out *api.Namespaces, s conversion.Scope) error { - out.Include = *(*[]string)(unsafe.Pointer(&in.Include)) - out.Exclude = *(*[]string)(unsafe.Pointer(&in.Exclude)) - return nil -} - -// Convert_v1alpha1_Namespaces_To_api_Namespaces is an autogenerated conversion function. -func Convert_v1alpha1_Namespaces_To_api_Namespaces(in *Namespaces, out *api.Namespaces, s conversion.Scope) error { - return autoConvert_v1alpha1_Namespaces_To_api_Namespaces(in, out, s) -} - -func autoConvert_api_Namespaces_To_v1alpha1_Namespaces(in *api.Namespaces, out *Namespaces, s conversion.Scope) error { - out.Include = *(*[]string)(unsafe.Pointer(&in.Include)) - out.Exclude = *(*[]string)(unsafe.Pointer(&in.Exclude)) - return nil -} - -// Convert_api_Namespaces_To_v1alpha1_Namespaces is an autogenerated conversion function. -func Convert_api_Namespaces_To_v1alpha1_Namespaces(in *api.Namespaces, out *Namespaces, s conversion.Scope) error { - return autoConvert_api_Namespaces_To_v1alpha1_Namespaces(in, out, s) -} - -func autoConvert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(in *NodeResourceUtilizationThresholds, out *api.NodeResourceUtilizationThresholds, s conversion.Scope) error { - out.UseDeviationThresholds = in.UseDeviationThresholds - out.Thresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.Thresholds)) - out.TargetThresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds)) - out.NumberOfNodes = in.NumberOfNodes - return nil -} - -// Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds is an autogenerated conversion function. -func Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(in *NodeResourceUtilizationThresholds, out *api.NodeResourceUtilizationThresholds, s conversion.Scope) error { - return autoConvert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(in, out, s) -} - -func autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in *api.NodeResourceUtilizationThresholds, out *NodeResourceUtilizationThresholds, s conversion.Scope) error { - out.UseDeviationThresholds = in.UseDeviationThresholds - out.Thresholds = *(*ResourceThresholds)(unsafe.Pointer(&in.Thresholds)) - out.TargetThresholds = *(*ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds)) - out.NumberOfNodes = in.NumberOfNodes - return nil -} - -// Convert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds is an autogenerated conversion function. -func Convert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in *api.NodeResourceUtilizationThresholds, out *NodeResourceUtilizationThresholds, s conversion.Scope) error { - return autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in, out, s) -} - -func autoConvert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in *PodLifeTime, out *api.PodLifeTime, s conversion.Scope) error { - out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) - out.States = *(*[]string)(unsafe.Pointer(&in.States)) - out.PodStatusPhases = *(*[]string)(unsafe.Pointer(&in.PodStatusPhases)) - return nil -} - -// Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime is an autogenerated conversion function. -func Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in *PodLifeTime, out *api.PodLifeTime, s conversion.Scope) error { - return autoConvert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in, out, s) -} - -func autoConvert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in *api.PodLifeTime, out *PodLifeTime, s conversion.Scope) error { - out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) - out.States = *(*[]string)(unsafe.Pointer(&in.States)) - out.PodStatusPhases = *(*[]string)(unsafe.Pointer(&in.PodStatusPhases)) - return nil -} - -// Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime is an autogenerated conversion function. -func Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in *api.PodLifeTime, out *PodLifeTime, s conversion.Scope) error { - return autoConvert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in, out, s) -} - -func autoConvert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in *PodsHavingTooManyRestarts, out *api.PodsHavingTooManyRestarts, s conversion.Scope) error { - out.PodRestartThreshold = in.PodRestartThreshold - out.IncludingInitContainers = in.IncludingInitContainers - return nil -} - -// Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts is an autogenerated conversion function. -func Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in *PodsHavingTooManyRestarts, out *api.PodsHavingTooManyRestarts, s conversion.Scope) error { - return autoConvert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in, out, s) -} - -func autoConvert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(in *api.PodsHavingTooManyRestarts, out *PodsHavingTooManyRestarts, s conversion.Scope) error { - out.PodRestartThreshold = in.PodRestartThreshold - out.IncludingInitContainers = in.IncludingInitContainers - return nil -} - -// Convert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts is an autogenerated conversion function. -func Convert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(in *api.PodsHavingTooManyRestarts, out *PodsHavingTooManyRestarts, s conversion.Scope) error { - return autoConvert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(in, out, s) -} - -func autoConvert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(in *RemoveDuplicates, out *api.RemoveDuplicates, s conversion.Scope) error { - out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds)) - return nil -} - -// Convert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates is an autogenerated conversion function. -func Convert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(in *RemoveDuplicates, out *api.RemoveDuplicates, s conversion.Scope) error { - return autoConvert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(in, out, s) -} - -func autoConvert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(in *api.RemoveDuplicates, out *RemoveDuplicates, s conversion.Scope) error { - out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds)) - return nil -} - -// Convert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates is an autogenerated conversion function. -func Convert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(in *api.RemoveDuplicates, out *RemoveDuplicates, s conversion.Scope) error { - return autoConvert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(in, out, s) -} - -func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *StrategyParameters, out *api.StrategyParameters, s conversion.Scope) error { - out.NodeResourceUtilizationThresholds = (*api.NodeResourceUtilizationThresholds)(unsafe.Pointer(in.NodeResourceUtilizationThresholds)) - out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType)) - out.PodsHavingTooManyRestarts = (*api.PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) - out.PodLifeTime = (*api.PodLifeTime)(unsafe.Pointer(in.PodLifeTime)) - out.RemoveDuplicates = (*api.RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) - out.FailedPods = (*api.FailedPods)(unsafe.Pointer(in.FailedPods)) - out.IncludeSoftConstraints = in.IncludeSoftConstraints - out.Namespaces = (*api.Namespaces)(unsafe.Pointer(in.Namespaces)) - out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority)) - out.ThresholdPriorityClassName = in.ThresholdPriorityClassName - out.LabelSelector = (*v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) - out.NodeFit = in.NodeFit - out.IncludePreferNoSchedule = in.IncludePreferNoSchedule - out.ExcludedTaints = *(*[]string)(unsafe.Pointer(&in.ExcludedTaints)) - return nil -} - -// Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters is an autogenerated conversion function. -func Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *StrategyParameters, out *api.StrategyParameters, s conversion.Scope) error { - return autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in, out, s) -} - -func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.StrategyParameters, out *StrategyParameters, s conversion.Scope) error { - out.NodeResourceUtilizationThresholds = (*NodeResourceUtilizationThresholds)(unsafe.Pointer(in.NodeResourceUtilizationThresholds)) - out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType)) - out.PodsHavingTooManyRestarts = (*PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) - out.PodLifeTime = (*PodLifeTime)(unsafe.Pointer(in.PodLifeTime)) - out.RemoveDuplicates = (*RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) - out.FailedPods = (*FailedPods)(unsafe.Pointer(in.FailedPods)) - out.IncludeSoftConstraints = in.IncludeSoftConstraints - out.Namespaces = (*Namespaces)(unsafe.Pointer(in.Namespaces)) - out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority)) - out.ThresholdPriorityClassName = in.ThresholdPriorityClassName - out.LabelSelector = (*v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) - out.NodeFit = in.NodeFit - out.IncludePreferNoSchedule = in.IncludePreferNoSchedule - out.ExcludedTaints = *(*[]string)(unsafe.Pointer(&in.ExcludedTaints)) - return nil -} - -// Convert_api_StrategyParameters_To_v1alpha1_StrategyParameters is an autogenerated conversion function. -func Convert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.StrategyParameters, out *StrategyParameters, s conversion.Scope) error { - return autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in, out, s) -} diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index ce880da15..2a45492c5 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -64,12 +64,12 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) { } if in.MaxNoOfPodsToEvictPerNode != nil { in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode - *out = new(int) + *out = new(uint) **out = **in } if in.MaxNoOfPodsToEvictPerNamespace != nil { in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace - *out = new(int) + *out = new(uint) **out = **in } return diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 2c360367f..71346b034 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -22,7 +22,6 @@ limitations under the License. package api import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -30,11 +29,11 @@ import ( func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) { *out = *in out.TypeMeta = in.TypeMeta - if in.Strategies != nil { - in, out := &in.Strategies, &out.Strategies - *out = make(StrategyList, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() + if in.Profiles != nil { + in, out := &in.Profiles, &out.Profiles + *out = make([]Profile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.NodeSelector != nil { @@ -42,26 +41,6 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) { *out = new(string) **out = **in } - if in.EvictFailedBarePods != nil { - in, out := &in.EvictFailedBarePods, &out.EvictFailedBarePods - *out = new(bool) - **out = **in - } - if in.EvictLocalStoragePods != nil { - in, out := &in.EvictLocalStoragePods, &out.EvictLocalStoragePods - *out = new(bool) - **out = **in - } - if in.EvictSystemCriticalPods != nil { - in, out := &in.EvictSystemCriticalPods, &out.EvictSystemCriticalPods - *out = new(bool) - **out = **in - } - if in.IgnorePVCPods != nil { - in, out := &in.IgnorePVCPods, &out.IgnorePVCPods - *out = new(bool) - **out = **in - } if in.MaxNoOfPodsToEvictPerNode != nil { in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode *out = new(uint) @@ -93,58 +72,6 @@ func (in *DeschedulerPolicy) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeschedulerStrategy) DeepCopyInto(out *DeschedulerStrategy) { - *out = *in - if in.Params != nil { - in, out := &in.Params, &out.Params - *out = new(StrategyParameters) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeschedulerStrategy. -func (in *DeschedulerStrategy) DeepCopy() *DeschedulerStrategy { - if in == nil { - return nil - } - out := new(DeschedulerStrategy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FailedPods) DeepCopyInto(out *FailedPods) { - *out = *in - if in.ExcludeOwnerKinds != nil { - in, out := &in.ExcludeOwnerKinds, &out.ExcludeOwnerKinds - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.MinPodLifetimeSeconds != nil { - in, out := &in.MinPodLifetimeSeconds, &out.MinPodLifetimeSeconds - *out = new(uint) - **out = **in - } - if in.Reasons != nil { - in, out := &in.Reasons, &out.Reasons - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailedPods. -func (in *FailedPods) DeepCopy() *FailedPods { - if in == nil { - return nil - } - out := new(FailedPods) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Namespaces) DeepCopyInto(out *Namespaces) { *out = *in @@ -172,78 +99,69 @@ func (in *Namespaces) DeepCopy() *Namespaces { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) { +func (in *PluginConfig) DeepCopyInto(out *PluginConfig) { *out = *in - if in.Thresholds != nil { - in, out := &in.Thresholds, &out.Thresholds - *out = make(ResourceThresholds, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.TargetThresholds != nil { - in, out := &in.TargetThresholds, &out.TargetThresholds - *out = make(ResourceThresholds, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + if in.Args != nil { + out.Args = in.Args.DeepCopyObject() } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResourceUtilizationThresholds. -func (in *NodeResourceUtilizationThresholds) DeepCopy() *NodeResourceUtilizationThresholds { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginConfig. +func (in *PluginConfig) DeepCopy() *PluginConfig { if in == nil { return nil } - out := new(NodeResourceUtilizationThresholds) + out := new(PluginConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodLifeTime) DeepCopyInto(out *PodLifeTime) { +func (in *PluginSet) DeepCopyInto(out *PluginSet) { *out = *in - if in.MaxPodLifeTimeSeconds != nil { - in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds - *out = new(uint) - **out = **in - } - if in.States != nil { - in, out := &in.States, &out.States + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled *out = make([]string, len(*in)) copy(*out, *in) } - if in.PodStatusPhases != nil { - in, out := &in.PodStatusPhases, &out.PodStatusPhases + if in.Disabled != nil { + in, out := &in.Disabled, &out.Disabled *out = make([]string, len(*in)) copy(*out, *in) } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLifeTime. -func (in *PodLifeTime) DeepCopy() *PodLifeTime { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginSet. +func (in *PluginSet) DeepCopy() *PluginSet { if in == nil { return nil } - out := new(PodLifeTime) + out := new(PluginSet) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodsHavingTooManyRestarts) DeepCopyInto(out *PodsHavingTooManyRestarts) { +func (in *Plugins) DeepCopyInto(out *Plugins) { *out = *in + in.PreSort.DeepCopyInto(&out.PreSort) + in.Sort.DeepCopyInto(&out.Sort) + in.Deschedule.DeepCopyInto(&out.Deschedule) + in.Balance.DeepCopyInto(&out.Balance) + in.Evict.DeepCopyInto(&out.Evict) + in.Filter.DeepCopyInto(&out.Filter) + in.PreEvictionFilter.DeepCopyInto(&out.PreEvictionFilter) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsHavingTooManyRestarts. -func (in *PodsHavingTooManyRestarts) DeepCopy() *PodsHavingTooManyRestarts { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plugins. +func (in *Plugins) DeepCopy() *Plugins { if in == nil { return nil } - out := new(PodsHavingTooManyRestarts) + out := new(Plugins) in.DeepCopyInto(out) return out } @@ -270,22 +188,25 @@ func (in *PriorityThreshold) DeepCopy() *PriorityThreshold { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RemoveDuplicates) DeepCopyInto(out *RemoveDuplicates) { +func (in *Profile) DeepCopyInto(out *Profile) { *out = *in - if in.ExcludeOwnerKinds != nil { - in, out := &in.ExcludeOwnerKinds, &out.ExcludeOwnerKinds - *out = make([]string, len(*in)) - copy(*out, *in) + if in.PluginConfigs != nil { + in, out := &in.PluginConfigs, &out.PluginConfigs + *out = make([]PluginConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } + in.Plugins.DeepCopyInto(&out.Plugins) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoveDuplicates. -func (in *RemoveDuplicates) DeepCopy() *RemoveDuplicates { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Profile. +func (in *Profile) DeepCopy() *Profile { if in == nil { return nil } - out := new(RemoveDuplicates) + out := new(Profile) in.DeepCopyInto(out) return out } @@ -311,91 +232,3 @@ func (in ResourceThresholds) DeepCopy() ResourceThresholds { in.DeepCopyInto(out) return *out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in StrategyList) DeepCopyInto(out *StrategyList) { - { - in := &in - *out = make(StrategyList, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - return - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrategyList. -func (in StrategyList) DeepCopy() StrategyList { - if in == nil { - return nil - } - out := new(StrategyList) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { - *out = *in - if in.NodeResourceUtilizationThresholds != nil { - in, out := &in.NodeResourceUtilizationThresholds, &out.NodeResourceUtilizationThresholds - *out = new(NodeResourceUtilizationThresholds) - (*in).DeepCopyInto(*out) - } - if in.NodeAffinityType != nil { - in, out := &in.NodeAffinityType, &out.NodeAffinityType - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.PodsHavingTooManyRestarts != nil { - in, out := &in.PodsHavingTooManyRestarts, &out.PodsHavingTooManyRestarts - *out = new(PodsHavingTooManyRestarts) - **out = **in - } - if in.PodLifeTime != nil { - in, out := &in.PodLifeTime, &out.PodLifeTime - *out = new(PodLifeTime) - (*in).DeepCopyInto(*out) - } - if in.RemoveDuplicates != nil { - in, out := &in.RemoveDuplicates, &out.RemoveDuplicates - *out = new(RemoveDuplicates) - (*in).DeepCopyInto(*out) - } - if in.FailedPods != nil { - in, out := &in.FailedPods, &out.FailedPods - *out = new(FailedPods) - (*in).DeepCopyInto(*out) - } - if in.Namespaces != nil { - in, out := &in.Namespaces, &out.Namespaces - *out = new(Namespaces) - (*in).DeepCopyInto(*out) - } - if in.ThresholdPriority != nil { - in, out := &in.ThresholdPriority, &out.ThresholdPriority - *out = new(int32) - **out = **in - } - if in.LabelSelector != nil { - in, out := &in.LabelSelector, &out.LabelSelector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.ExcludedTaints != nil { - in, out := &in.ExcludedTaints, &out.ExcludedTaints - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrategyParameters. -func (in *StrategyParameters) DeepCopy() *StrategyParameters { - if in == nil { - return nil - } - out := new(StrategyParameters) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/descheduler/descheduler.go b/pkg/descheduler/descheduler.go index 6d5d1ff24..672c6ebd8 100644 --- a/pkg/descheduler/descheduler.go +++ b/pkg/descheduler/descheduler.go @@ -58,7 +58,7 @@ func Run(ctx context.Context, rs *options.DeschedulerServer) error { rs.Client = rsclient rs.EventClient = eventClient - deschedulerPolicy, err := LoadPolicyConfig(rs.PolicyConfigFile) + deschedulerPolicy, err := LoadPolicyConfig(rs.PolicyConfigFile, rs.Client) if err != nil { return err } @@ -93,8 +93,6 @@ func Run(ctx context.Context, rs *options.DeschedulerServer) error { return runFn() } -type strategyFunction func(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor, evictorFilter framework.EvictorPlugin, getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc) - func cachedClient( realClient clientset.Interface, podLister listersv1.PodLister, @@ -248,50 +246,11 @@ func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer sharedInformerFactory.Start(ctx.Done()) sharedInformerFactory.WaitForCacheSync(ctx.Done()) - strategyFuncs := map[api.StrategyName]strategyFunction{ - "RemoveDuplicates": nil, - "LowNodeUtilization": nil, - "HighNodeUtilization": nil, - "RemovePodsViolatingInterPodAntiAffinity": nil, - "RemovePodsViolatingNodeAffinity": nil, - "RemovePodsViolatingNodeTaints": nil, - "RemovePodsHavingTooManyRestarts": nil, - "PodLifeTime": nil, - "RemovePodsViolatingTopologySpreadConstraint": nil, - "RemoveFailedPods": nil, - } - var nodeSelector string if deschedulerPolicy.NodeSelector != nil { nodeSelector = *deschedulerPolicy.NodeSelector } - var evictLocalStoragePods bool - if deschedulerPolicy.EvictLocalStoragePods != nil { - evictLocalStoragePods = *deschedulerPolicy.EvictLocalStoragePods - } - - evictBarePods := false - if deschedulerPolicy.EvictFailedBarePods != nil { - evictBarePods = *deschedulerPolicy.EvictFailedBarePods - if evictBarePods { - klog.V(1).InfoS("Warning: EvictFailedBarePods is set to True. This could cause eviction of pods without ownerReferences.") - } - } - - evictSystemCriticalPods := false - if deschedulerPolicy.EvictSystemCriticalPods != nil { - evictSystemCriticalPods = *deschedulerPolicy.EvictSystemCriticalPods - if evictSystemCriticalPods { - klog.V(1).InfoS("Warning: EvictSystemCriticalPods is set to True. This could cause eviction of Kubernetes system pods.") - } - } - - ignorePvcPods := false - if deschedulerPolicy.IgnorePVCPods != nil { - ignorePvcPods = *deschedulerPolicy.IgnorePVCPods - } - var eventClient clientset.Interface if rs.DryRun { eventClient = fakeclientset.NewSimpleClientset() @@ -358,74 +317,86 @@ func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer eventRecorder, ) - for name, strategy := range deschedulerPolicy.Strategies { - if f, ok := strategyFuncs[name]; ok { - if strategy.Enabled { - params := strategy.Params - if params == nil { - params = &api.StrategyParameters{} - } + var enabledDeschedulePlugins []framework.DeschedulePlugin + var enabledBalancePlugins []framework.BalancePlugin - nodeFit := false - if name != "PodLifeTime" { - nodeFit = params.NodeFit - } - - // TODO(jchaloup): once all strategies are migrated move this check under - // the default evictor args validation - if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { - klog.V(1).ErrorS(fmt.Errorf("priority threshold misconfigured"), "only one of priorityThreshold fields can be set", "pluginName", name) - continue - } - thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, rs.Client, strategy.Params) - if err != nil { - klog.ErrorS(err, "Failed to get threshold priority from strategy's params") - continue - } - - defaultevictorArgs := &defaultevictor.DefaultEvictorArgs{ - EvictLocalStoragePods: evictLocalStoragePods, - EvictSystemCriticalPods: evictSystemCriticalPods, - IgnorePvcPods: ignorePvcPods, - EvictFailedBarePods: evictBarePods, - NodeFit: nodeFit, - PriorityThreshold: &api.PriorityThreshold{ - Value: &thresholdPriority, - }, - } - - evictorFilter, _ := defaultevictor.New( - defaultevictorArgs, - &handleImpl{ - clientSet: rs.Client, - getPodsAssignedToNodeFunc: getPodsAssignedToNode, - sharedInformerFactory: sharedInformerFactory, - }, - ) - - handle := &handleImpl{ - clientSet: rs.Client, - getPodsAssignedToNodeFunc: getPodsAssignedToNode, - sharedInformerFactory: sharedInformerFactory, - evictor: &evictorImpl{ - podEvictor: podEvictor, - evictorFilter: evictorFilter.(framework.EvictorPlugin), - }, - } - - // TODO: strategyName should be accessible from within the strategy using a framework - // handle or function which the Evictor has access to. For migration/in-progress framework - // work, we are currently passing this via context. To be removed - // (See discussion thread https://github.com/kubernetes-sigs/descheduler/pull/885#discussion_r919962292) - childCtx := context.WithValue(ctx, "strategyName", string(name)) - if pgFnc, exists := pluginsMap[string(name)]; exists { - pgFnc(childCtx, nodes, params, handle) - } else { - f(childCtx, rs.Client, strategy, nodes, podEvictor, evictorFilter.(framework.EvictorPlugin), getPodsAssignedToNode) + // Build plugins + for _, profile := range deschedulerPolicy.Profiles { + pc := getPluginConfig(defaultevictor.PluginName, profile.PluginConfigs) + if pc == nil { + klog.ErrorS(fmt.Errorf("unable to get plugin config"), "skipping plugin", "plugin", defaultevictor.PluginName, "profile", profile.Name) + continue + } + evictorFilter, err := defaultevictor.New( + pc.Args, + &handleImpl{ + clientSet: rs.Client, + getPodsAssignedToNodeFunc: getPodsAssignedToNode, + sharedInformerFactory: sharedInformerFactory, + }, + ) + if err != nil { + klog.ErrorS(fmt.Errorf("unable to construct a plugin"), "skipping plugin", "plugin", defaultevictor.PluginName) + continue + } + handle := &handleImpl{ + clientSet: rs.Client, + getPodsAssignedToNodeFunc: getPodsAssignedToNode, + sharedInformerFactory: sharedInformerFactory, + evictor: &evictorImpl{ + podEvictor: podEvictor, + evictorFilter: evictorFilter.(framework.EvictorPlugin), + }, + } + // Assuming only a list of enabled extension points. + // Later, when a default list of plugins and their extension points is established, + // compute the list of enabled extension points as (DefaultEnabled + Enabled - Disabled) + for _, plugin := range append(profile.Plugins.Deschedule.Enabled, profile.Plugins.Balance.Enabled...) { + pc := getPluginConfig(plugin, profile.PluginConfigs) + if pc == nil { + klog.ErrorS(fmt.Errorf("unable to get plugin config"), "skipping plugin", "plugin", plugin) + continue + } + pgFnc, ok := pluginsMap[plugin] + if !ok { + klog.ErrorS(fmt.Errorf("unable to find plugin in the pluginsMap"), "skipping plugin", "plugin", plugin) + } + pg := pgFnc(pc.Args, handle) + if pg != nil { + switch v := pg.(type) { + case framework.DeschedulePlugin: + enabledDeschedulePlugins = append(enabledDeschedulePlugins, v) + case framework.BalancePlugin: + enabledBalancePlugins = append(enabledBalancePlugins, v) + default: + klog.ErrorS(fmt.Errorf("unknown plugin extension point"), "skipping plugin", "plugin", plugin) } } - } else { - klog.ErrorS(fmt.Errorf("unknown strategy name"), "skipping strategy", "strategy", name) + } + } + + // Execute extension points + for _, pg := range enabledDeschedulePlugins { + // TODO: strategyName should be accessible from within the strategy using a framework + // handle or function which the Evictor has access to. For migration/in-progress framework + // work, we are currently passing this via context. To be removed + // (See discussion thread https://github.com/kubernetes-sigs/descheduler/pull/885#discussion_r919962292) + childCtx := context.WithValue(ctx, "strategyName", pg.Name()) + status := pg.Deschedule(childCtx, nodes) + if status != nil && status.Err != nil { + klog.ErrorS(status.Err, "plugin finished with error", "pluginName", pg.Name()) + } + } + + for _, pg := range enabledBalancePlugins { + // TODO: strategyName should be accessible from within the strategy using a framework + // handle or function which the Evictor has access to. For migration/in-progress framework + // work, we are currently passing this via context. To be removed + // (See discussion thread https://github.com/kubernetes-sigs/descheduler/pull/885#discussion_r919962292) + childCtx := context.WithValue(ctx, "strategyName", pg.Name()) + status := pg.Balance(childCtx, nodes) + if status != nil && status.Err != nil { + klog.ErrorS(status.Err, "plugin finished with error", "pluginName", pg.Name()) } } @@ -440,6 +411,15 @@ func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer return nil } +func getPluginConfig(pluginName string, pluginConfigs []api.PluginConfig) *api.PluginConfig { + for _, pluginConfig := range pluginConfigs { + if pluginConfig.Name == pluginName { + return &pluginConfig + } + } + return nil +} + func createClients(kubeconfig string) (clientset.Interface, clientset.Interface, error) { kClient, err := client.CreateClient(kubeconfig, "descheduler") if err != nil { diff --git a/pkg/descheduler/descheduler_test.go b/pkg/descheduler/descheduler_test.go index 224107411..e2f6b3bf2 100644 --- a/pkg/descheduler/descheduler_test.go +++ b/pkg/descheduler/descheduler_test.go @@ -14,6 +14,7 @@ import ( core "k8s.io/client-go/testing" "sigs.k8s.io/descheduler/cmd/descheduler/app/options" "sigs.k8s.io/descheduler/pkg/api" + "sigs.k8s.io/descheduler/pkg/api/v1alpha1" "sigs.k8s.io/descheduler/test" ) @@ -29,9 +30,9 @@ func TestTaintsUpdated(t *testing.T) { client := fakeclientset.NewSimpleClientset(n1, n2, p1) eventClient := fakeclientset.NewSimpleClientset(n1, n2, p1) - dp := &api.DeschedulerPolicy{ - Strategies: api.StrategyList{ - "RemovePodsViolatingNodeTaints": api.DeschedulerStrategy{ + dp := &v1alpha1.DeschedulerPolicy{ + Strategies: v1alpha1.StrategyList{ + "RemovePodsViolatingNodeTaints": v1alpha1.DeschedulerStrategy{ Enabled: true, }, }, @@ -68,7 +69,12 @@ func TestTaintsUpdated(t *testing.T) { var evictedPods []string client.PrependReactor("create", "pods", podEvictionReactionFuc(&evictedPods)) - if err := RunDeschedulerStrategies(ctx, rs, dp, "v1"); err != nil { + internalDeschedulerPolicy, err := V1alpha1ToInternal(client, dp) + if err != nil { + t.Fatalf("Unable to convert v1alpha1 to v1alpha2: %v", err) + } + + if err := RunDeschedulerStrategies(ctx, rs, internalDeschedulerPolicy, "v1"); err != nil { t.Fatalf("Unable to run descheduler strategies: %v", err) } @@ -96,9 +102,9 @@ func TestDuplicate(t *testing.T) { client := fakeclientset.NewSimpleClientset(node1, node2, p1, p2, p3) eventClient := fakeclientset.NewSimpleClientset(node1, node2, p1, p2, p3) - dp := &api.DeschedulerPolicy{ - Strategies: api.StrategyList{ - "RemoveDuplicates": api.DeschedulerStrategy{ + dp := &v1alpha1.DeschedulerPolicy{ + Strategies: v1alpha1.StrategyList{ + "RemoveDuplicates": v1alpha1.DeschedulerStrategy{ Enabled: true, }, }, @@ -123,7 +129,11 @@ func TestDuplicate(t *testing.T) { var evictedPods []string client.PrependReactor("create", "pods", podEvictionReactionFuc(&evictedPods)) - if err := RunDeschedulerStrategies(ctx, rs, dp, "v1"); err != nil { + internalDeschedulerPolicy, err := V1alpha1ToInternal(client, dp) + if err != nil { + t.Fatalf("Unable to convert v1alpha1 to v1alpha2: %v", err) + } + if err := RunDeschedulerStrategies(ctx, rs, internalDeschedulerPolicy, "v1"); err != nil { t.Fatalf("Unable to run descheduler strategies: %v", err) } @@ -139,7 +149,7 @@ func TestRootCancel(t *testing.T) { client := fakeclientset.NewSimpleClientset(n1, n2) eventClient := fakeclientset.NewSimpleClientset(n1, n2) dp := &api.DeschedulerPolicy{ - Strategies: api.StrategyList{}, // no strategies needed for this test + Profiles: []api.Profile{}, // no strategies needed for this test } rs, err := options.NewDeschedulerServer() @@ -174,7 +184,7 @@ func TestRootCancelWithNoInterval(t *testing.T) { client := fakeclientset.NewSimpleClientset(n1, n2) eventClient := fakeclientset.NewSimpleClientset(n1, n2) dp := &api.DeschedulerPolicy{ - Strategies: api.StrategyList{}, // no strategies needed for this test + Profiles: []api.Profile{}, // no strategies needed for this test } rs, err := options.NewDeschedulerServer() diff --git a/pkg/descheduler/policyconfig.go b/pkg/descheduler/policyconfig.go index 20bfa209d..dab515be1 100644 --- a/pkg/descheduler/policyconfig.go +++ b/pkg/descheduler/policyconfig.go @@ -17,18 +17,22 @@ limitations under the License. package descheduler import ( + "context" "fmt" "io/ioutil" "k8s.io/apimachinery/pkg/runtime" + clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" "sigs.k8s.io/descheduler/pkg/api" "sigs.k8s.io/descheduler/pkg/api/v1alpha1" "sigs.k8s.io/descheduler/pkg/descheduler/scheme" + "sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor" + "sigs.k8s.io/descheduler/pkg/utils" ) -func LoadPolicyConfig(policyConfigFile string) (*api.DeschedulerPolicy, error) { +func LoadPolicyConfig(policyConfigFile string, client clientset.Interface) (*api.DeschedulerPolicy, error) { if policyConfigFile == "" { klog.V(1).InfoS("Policy config file not specified") return nil, nil @@ -46,10 +50,150 @@ func LoadPolicyConfig(policyConfigFile string) (*api.DeschedulerPolicy, error) { return nil, fmt.Errorf("failed decoding descheduler's policy config %q: %v", policyConfigFile, err) } - internalPolicy := &api.DeschedulerPolicy{} - if err := scheme.Scheme.Convert(versionedPolicy, internalPolicy, nil); err != nil { + // Build profiles + // TODO(jchaloup): replace this with v1alpha1 -> v1alpha2 conversion + internalPolicy, err := V1alpha1ToInternal(client, versionedPolicy) + if err != nil { return nil, fmt.Errorf("failed converting versioned policy to internal policy version: %v", err) } return internalPolicy, nil } + +func V1alpha1ToInternal( + client clientset.Interface, + deschedulerPolicy *v1alpha1.DeschedulerPolicy, +) (*api.DeschedulerPolicy, error) { + validStrategyNames := map[v1alpha1.StrategyName]interface{}{ + "RemoveDuplicates": nil, + "LowNodeUtilization": nil, + "HighNodeUtilization": nil, + "RemovePodsViolatingInterPodAntiAffinity": nil, + "RemovePodsViolatingNodeAffinity": nil, + "RemovePodsViolatingNodeTaints": nil, + "RemovePodsHavingTooManyRestarts": nil, + "PodLifeTime": nil, + "RemovePodsViolatingTopologySpreadConstraint": nil, + "RemoveFailedPods": nil, + } + + var evictLocalStoragePods bool + if deschedulerPolicy.EvictLocalStoragePods != nil { + evictLocalStoragePods = *deschedulerPolicy.EvictLocalStoragePods + } + + evictBarePods := false + if deschedulerPolicy.EvictFailedBarePods != nil { + evictBarePods = *deschedulerPolicy.EvictFailedBarePods + if evictBarePods { + klog.V(1).InfoS("Warning: EvictFailedBarePods is set to True. This could cause eviction of pods without ownerReferences.") + } + } + + evictSystemCriticalPods := false + if deschedulerPolicy.EvictSystemCriticalPods != nil { + evictSystemCriticalPods = *deschedulerPolicy.EvictSystemCriticalPods + if evictSystemCriticalPods { + klog.V(1).InfoS("Warning: EvictSystemCriticalPods is set to True. This could cause eviction of Kubernetes system pods.") + } + } + + ignorePvcPods := false + if deschedulerPolicy.IgnorePVCPods != nil { + ignorePvcPods = *deschedulerPolicy.IgnorePVCPods + } + + var profiles []api.Profile + + // Build profiles + for name, strategy := range deschedulerPolicy.Strategies { + if _, ok := validStrategyNames[name]; ok { + if strategy.Enabled { + params := strategy.Params + if params == nil { + params = &v1alpha1.StrategyParameters{} + } + + nodeFit := false + if name != "PodLifeTime" { + nodeFit = params.NodeFit + } + + // TODO(jchaloup): once all strategies are migrated move this check under + // the default evictor args validation + if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { + klog.ErrorS(fmt.Errorf("priority threshold misconfigured"), "only one of priorityThreshold fields can be set", "pluginName", name) + return nil, fmt.Errorf("priority threshold misconfigured for plugin %v", name) + } + var priorityThreshold *api.PriorityThreshold + if strategy.Params != nil { + priorityThreshold = &api.PriorityThreshold{ + Value: strategy.Params.ThresholdPriority, + Name: strategy.Params.ThresholdPriorityClassName, + } + } + thresholdPriority, err := utils.GetPriorityFromStrategyParams(context.TODO(), client, priorityThreshold) + if err != nil { + klog.ErrorS(err, "Failed to get threshold priority from strategy's params") + return nil, fmt.Errorf("failed to get threshold priority from strategy's params: %v", err) + } + + var pluginConfig *api.PluginConfig + if pcFnc, exists := strategyParamsToPluginArgs[string(name)]; exists { + pluginConfig, err = pcFnc(params) + if err != nil { + klog.ErrorS(err, "skipping strategy", "strategy", name) + return nil, fmt.Errorf("failed to get plugin config for strategy %v: %v", name, err) + } + } else { + klog.ErrorS(fmt.Errorf("unknown strategy name"), "skipping strategy", "strategy", name) + return nil, fmt.Errorf("unknown strategy name: %v", name) + } + + profile := api.Profile{ + Name: fmt.Sprintf("strategy-%v-profile", name), + PluginConfigs: []api.PluginConfig{ + { + Name: defaultevictor.PluginName, + Args: &defaultevictor.DefaultEvictorArgs{ + EvictLocalStoragePods: evictLocalStoragePods, + EvictSystemCriticalPods: evictSystemCriticalPods, + IgnorePvcPods: ignorePvcPods, + EvictFailedBarePods: evictBarePods, + NodeFit: nodeFit, + PriorityThreshold: &api.PriorityThreshold{ + Value: &thresholdPriority, + }, + }, + }, + *pluginConfig, + }, + Plugins: api.Plugins{ + Evict: api.PluginSet{ + Enabled: []string{defaultevictor.PluginName}, + }, + }, + } + + // Plugins have either of the two extension points + switch pluginToExtensionPoint[pluginConfig.Name] { + case descheduleEP: + profile.Plugins.Deschedule.Enabled = []string{pluginConfig.Name} + case balanceEP: + profile.Plugins.Balance.Enabled = []string{pluginConfig.Name} + } + profiles = append(profiles, profile) + } + } else { + klog.ErrorS(fmt.Errorf("unknown strategy name"), "skipping strategy", "strategy", name) + return nil, fmt.Errorf("unknown strategy name: %v", name) + } + } + + return &api.DeschedulerPolicy{ + Profiles: profiles, + NodeSelector: deschedulerPolicy.NodeSelector, + MaxNoOfPodsToEvictPerNode: deschedulerPolicy.MaxNoOfPodsToEvictPerNode, + MaxNoOfPodsToEvictPerNamespace: deschedulerPolicy.MaxNoOfPodsToEvictPerNamespace, + }, nil +} diff --git a/pkg/descheduler/policyconfig_test.go b/pkg/descheduler/policyconfig_test.go new file mode 100644 index 000000000..6e3d38195 --- /dev/null +++ b/pkg/descheduler/policyconfig_test.go @@ -0,0 +1,699 @@ +/* +Copyright 2017 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 descheduler + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + fakeclientset "k8s.io/client-go/kubernetes/fake" + utilpointer "k8s.io/utils/pointer" + "sigs.k8s.io/descheduler/pkg/api" + "sigs.k8s.io/descheduler/pkg/api/v1alpha1" + "sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor" + "sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removeduplicates" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removefailedpods" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodshavingtoomanyrestarts" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatinginterpodantiaffinity" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatingnodeaffinity" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatingnodetaints" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatingtopologyspreadconstraint" + "sigs.k8s.io/descheduler/pkg/utils" +) + +func TestV1alpha1ToV1alpha2(t *testing.T) { + defaultEvictorPluginConfig := api.PluginConfig{ + Name: defaultevictor.PluginName, + Args: &defaultevictor.DefaultEvictorArgs{ + PriorityThreshold: &api.PriorityThreshold{ + Value: utilpointer.Int32(utils.SystemCriticalPriority), + }, + }, + } + defaultEvictorPluginSet := api.PluginSet{ + Enabled: []string{defaultevictor.PluginName}, + } + type testCase struct { + description string + policy *v1alpha1.DeschedulerPolicy + err error + result *api.DeschedulerPolicy + } + testCases := []testCase{ + { + description: "RemoveFailedPods enabled, LowNodeUtilization disabled strategies to profile", + policy: &v1alpha1.DeschedulerPolicy{ + Strategies: v1alpha1.StrategyList{ + removeduplicates.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{ + "test2", + }, + }, + }, + }, + nodeutilization.LowNodeUtilizationPluginName: v1alpha1.DeschedulerStrategy{ + Enabled: false, + Params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + TargetThresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(50), + "memory": v1alpha1.Percentage(50), + "pods": v1alpha1.Percentage(50), + }, + }, + }, + }, + }, + }, + result: &api.DeschedulerPolicy{ + Profiles: []api.Profile{ + { + Name: fmt.Sprintf("strategy-%s-profile", removeduplicates.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removeduplicates.PluginName, + Args: &removeduplicates.RemoveDuplicatesArgs{ + Namespaces: &api.Namespaces{ + Exclude: []string{ + "test2", + }, + }, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{removeduplicates.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + // Disabled strategy is not generating internal plugin since it is not being used internally currently + // { + // Name: nodeutilization.LowNodeUtilizationPluginName, + // PluginConfigs: []api.PluginConfig{ + // { + // Name: nodeutilization.LowNodeUtilizationPluginName, + // Args: &nodeutilization.LowNodeUtilizationArgs{ + // Thresholds: api.ResourceThresholds{ + // "cpu": api.Percentage(20), + // [...] + // [...] + // }, + }, + }, + }, + { + description: "convert all strategies", + policy: &v1alpha1.DeschedulerPolicy{ + Strategies: v1alpha1.StrategyList{ + removeduplicates.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + nodeutilization.LowNodeUtilizationPluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + TargetThresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(50), + "memory": v1alpha1.Percentage(50), + "pods": v1alpha1.Percentage(50), + }, + }, + }, + }, + nodeutilization.HighNodeUtilizationPluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + }, + }, + }, + removefailedpods.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + removepodshavingtoomanyrestarts.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + PodsHavingTooManyRestarts: &v1alpha1.PodsHavingTooManyRestarts{ + PodRestartThreshold: 100, + }, + }, + }, + removepodsviolatinginterpodantiaffinity.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + removepodsviolatingnodeaffinity.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + }, + }, + removepodsviolatingnodetaints.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + removepodsviolatingtopologyspreadconstraint.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + }, + }, + result: &api.DeschedulerPolicy{ + Profiles: []api.Profile{ + { + Name: fmt.Sprintf("strategy-%s-profile", nodeutilization.HighNodeUtilizationPluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: nodeutilization.HighNodeUtilizationPluginName, + Args: &nodeutilization.HighNodeUtilizationArgs{ + Thresholds: api.ResourceThresholds{ + "cpu": api.Percentage(20), + "memory": api.Percentage(20), + "pods": api.Percentage(20), + }, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{nodeutilization.HighNodeUtilizationPluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", nodeutilization.LowNodeUtilizationPluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: nodeutilization.LowNodeUtilizationPluginName, + Args: &nodeutilization.LowNodeUtilizationArgs{ + Thresholds: api.ResourceThresholds{ + "cpu": api.Percentage(20), + "memory": api.Percentage(20), + "pods": api.Percentage(20), + }, + TargetThresholds: api.ResourceThresholds{ + "cpu": api.Percentage(50), + "memory": api.Percentage(50), + "pods": api.Percentage(50), + }, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{nodeutilization.LowNodeUtilizationPluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removeduplicates.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removeduplicates.PluginName, + Args: &removeduplicates.RemoveDuplicatesArgs{}, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{removeduplicates.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removefailedpods.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removefailedpods.PluginName, + Args: &removefailedpods.RemoveFailedPodsArgs{}, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removefailedpods.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodshavingtoomanyrestarts.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodshavingtoomanyrestarts.PluginName, + Args: &removepodshavingtoomanyrestarts.RemovePodsHavingTooManyRestartsArgs{ + PodRestartThreshold: 100, + }, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodshavingtoomanyrestarts.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatinginterpodantiaffinity.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatinginterpodantiaffinity.PluginName, + Args: &removepodsviolatinginterpodantiaffinity.RemovePodsViolatingInterPodAntiAffinityArgs{}, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodsviolatinginterpodantiaffinity.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatingnodeaffinity.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatingnodeaffinity.PluginName, + Args: &removepodsviolatingnodeaffinity.RemovePodsViolatingNodeAffinityArgs{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + }, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodsviolatingnodeaffinity.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatingnodetaints.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatingnodetaints.PluginName, + Args: &removepodsviolatingnodetaints.RemovePodsViolatingNodeTaintsArgs{}, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodsviolatingnodetaints.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatingtopologyspreadconstraint.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatingtopologyspreadconstraint.PluginName, + Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{}, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{removepodsviolatingtopologyspreadconstraint.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + }, + }, + }, + { + description: "pass in all params to check args", + policy: &v1alpha1.DeschedulerPolicy{ + Strategies: v1alpha1.StrategyList{ + removeduplicates.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + RemoveDuplicates: &v1alpha1.RemoveDuplicates{ + ExcludeOwnerKinds: []string{"ReplicaSet"}, + }, + }, + }, + nodeutilization.LowNodeUtilizationPluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + TargetThresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(50), + "memory": v1alpha1.Percentage(50), + "pods": v1alpha1.Percentage(50), + }, + UseDeviationThresholds: true, + NumberOfNodes: 3, + }, + }, + }, + nodeutilization.HighNodeUtilizationPluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + }, + }, + }, + removefailedpods.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + FailedPods: &v1alpha1.FailedPods{ + MinPodLifetimeSeconds: utilpointer.Uint(3600), + ExcludeOwnerKinds: []string{"Job"}, + Reasons: []string{"NodeAffinity"}, + IncludingInitContainers: true, + }, + }, + }, + removepodshavingtoomanyrestarts.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + PodsHavingTooManyRestarts: &v1alpha1.PodsHavingTooManyRestarts{ + PodRestartThreshold: 100, + IncludingInitContainers: true, + }, + }, + }, + removepodsviolatinginterpodantiaffinity.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + removepodsviolatingnodeaffinity.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + }, + }, + removepodsviolatingnodetaints.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + ExcludedTaints: []string{"dedicated=special-user", "reserved"}, + }, + }, + removepodsviolatingtopologyspreadconstraint.PluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + IncludeSoftConstraints: true, + }, + }, + }, + }, + result: &api.DeschedulerPolicy{ + Profiles: []api.Profile{ + { + Name: fmt.Sprintf("strategy-%s-profile", nodeutilization.HighNodeUtilizationPluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: nodeutilization.HighNodeUtilizationPluginName, + Args: &nodeutilization.HighNodeUtilizationArgs{ + Thresholds: api.ResourceThresholds{ + "cpu": api.Percentage(20), + "memory": api.Percentage(20), + "pods": api.Percentage(20), + }, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{nodeutilization.HighNodeUtilizationPluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", nodeutilization.LowNodeUtilizationPluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: nodeutilization.LowNodeUtilizationPluginName, + Args: &nodeutilization.LowNodeUtilizationArgs{ + UseDeviationThresholds: true, + NumberOfNodes: 3, + Thresholds: api.ResourceThresholds{ + "cpu": api.Percentage(20), + "memory": api.Percentage(20), + "pods": api.Percentage(20), + }, + TargetThresholds: api.ResourceThresholds{ + "cpu": api.Percentage(50), + "memory": api.Percentage(50), + "pods": api.Percentage(50), + }, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{nodeutilization.LowNodeUtilizationPluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removeduplicates.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removeduplicates.PluginName, + Args: &removeduplicates.RemoveDuplicatesArgs{ + ExcludeOwnerKinds: []string{"ReplicaSet"}, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{removeduplicates.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removefailedpods.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removefailedpods.PluginName, + Args: &removefailedpods.RemoveFailedPodsArgs{ + ExcludeOwnerKinds: []string{"Job"}, + MinPodLifetimeSeconds: utilpointer.Uint(3600), + Reasons: []string{"NodeAffinity"}, + IncludingInitContainers: true, + }, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removefailedpods.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodshavingtoomanyrestarts.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodshavingtoomanyrestarts.PluginName, + Args: &removepodshavingtoomanyrestarts.RemovePodsHavingTooManyRestartsArgs{ + PodRestartThreshold: 100, + IncludingInitContainers: true, + }, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodshavingtoomanyrestarts.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatinginterpodantiaffinity.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatinginterpodantiaffinity.PluginName, + Args: &removepodsviolatinginterpodantiaffinity.RemovePodsViolatingInterPodAntiAffinityArgs{}, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodsviolatinginterpodantiaffinity.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatingnodeaffinity.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatingnodeaffinity.PluginName, + Args: &removepodsviolatingnodeaffinity.RemovePodsViolatingNodeAffinityArgs{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + }, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodsviolatingnodeaffinity.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatingnodetaints.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatingnodetaints.PluginName, + Args: &removepodsviolatingnodetaints.RemovePodsViolatingNodeTaintsArgs{ + ExcludedTaints: []string{"dedicated=special-user", "reserved"}, + }, + }, + }, + Plugins: api.Plugins{ + Deschedule: api.PluginSet{ + Enabled: []string{removepodsviolatingnodetaints.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + { + Name: fmt.Sprintf("strategy-%s-profile", removepodsviolatingtopologyspreadconstraint.PluginName), + PluginConfigs: []api.PluginConfig{ + defaultEvictorPluginConfig, + { + Name: removepodsviolatingtopologyspreadconstraint.PluginName, + Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{ + IncludeSoftConstraints: true, + }, + }, + }, + Plugins: api.Plugins{ + Balance: api.PluginSet{ + Enabled: []string{removepodsviolatingtopologyspreadconstraint.PluginName}, + }, + Evict: defaultEvictorPluginSet, + }, + }, + }, + }, + }, + { + description: "invalid strategy name", + policy: &v1alpha1.DeschedulerPolicy{Strategies: v1alpha1.StrategyList{ + "InvalidName": v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{}, + }, + }}, + result: nil, + err: fmt.Errorf("unknown strategy name: InvalidName"), + }, + { + description: "invalid threshold priority", + policy: &v1alpha1.DeschedulerPolicy{Strategies: v1alpha1.StrategyList{ + nodeutilization.LowNodeUtilizationPluginName: v1alpha1.DeschedulerStrategy{ + Enabled: true, + Params: &v1alpha1.StrategyParameters{ + ThresholdPriority: utilpointer.Int32(100), + ThresholdPriorityClassName: "name", + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + TargetThresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(50), + "memory": v1alpha1.Percentage(50), + "pods": v1alpha1.Percentage(50), + }, + }, + }, + }, + }}, + result: nil, + err: fmt.Errorf("priority threshold misconfigured for plugin LowNodeUtilization"), + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + client := fakeclientset.NewSimpleClientset() + result, err := V1alpha1ToInternal(client, tc.policy) + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + result.Profiles = api.SortProfilesByName(result.Profiles) + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} diff --git a/pkg/descheduler/strategies/validation/strategyparams.go b/pkg/descheduler/strategies/validation/strategyparams.go deleted file mode 100644 index 33446e6fa..000000000 --- a/pkg/descheduler/strategies/validation/strategyparams.go +++ /dev/null @@ -1,71 +0,0 @@ -package validation - -import ( - "context" - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - - "sigs.k8s.io/descheduler/pkg/api" - "sigs.k8s.io/descheduler/pkg/utils" -) - -// ValidatedStrategyParams contains validated common strategy parameters -type ValidatedStrategyParams struct { - ThresholdPriority int32 - IncludedNamespaces sets.String - ExcludedNamespaces sets.String - LabelSelector labels.Selector - NodeFit bool -} - -func DefaultValidatedStrategyParams() ValidatedStrategyParams { - return ValidatedStrategyParams{ThresholdPriority: utils.SystemCriticalPriority} -} - -func ValidateAndParseStrategyParams( - ctx context.Context, - client clientset.Interface, - params *api.StrategyParameters, -) (*ValidatedStrategyParams, error) { - if params == nil { - defaultValidatedStrategyParams := DefaultValidatedStrategyParams() - return &defaultValidatedStrategyParams, nil - } - - // At most one of include/exclude can be set - var includedNamespaces, excludedNamespaces sets.String - if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { - return nil, fmt.Errorf("only one of Include/Exclude namespaces can be set") - } - if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { - return nil, fmt.Errorf("only one of ThresholdPriority and thresholdPriorityClassName can be set") - } - - thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, params) - if err != nil { - return nil, fmt.Errorf("failed to get threshold priority from strategy's params: %+v", err) - } - if params.Namespaces != nil { - includedNamespaces = sets.NewString(params.Namespaces.Include...) - excludedNamespaces = sets.NewString(params.Namespaces.Exclude...) - } - var selector labels.Selector - if params.LabelSelector != nil { - selector, err = metav1.LabelSelectorAsSelector(params.LabelSelector) - if err != nil { - return nil, fmt.Errorf("failed to get label selectors from strategy's params: %+v", err) - } - } - - return &ValidatedStrategyParams{ - ThresholdPriority: thresholdPriority, - IncludedNamespaces: includedNamespaces, - ExcludedNamespaces: excludedNamespaces, - LabelSelector: selector, - NodeFit: params.NodeFit, - }, nil -} diff --git a/pkg/descheduler/strategies/validation/strategyparams_test.go b/pkg/descheduler/strategies/validation/strategyparams_test.go deleted file mode 100644 index 7f3638164..000000000 --- a/pkg/descheduler/strategies/validation/strategyparams_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package validation - -import ( - "context" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - - "sigs.k8s.io/descheduler/pkg/api" -) - -var thresholdPriority int32 = 1000 - -func TestValidStrategyParams(t *testing.T) { - ctx := context.Background() - fakeClient := &fake.Clientset{} - testCases := []struct { - name string - params *api.StrategyParameters - }{ - {name: "validate nil params", params: nil}, - {name: "validate empty params", params: &api.StrategyParameters{}}, - {name: "validate params with NodeFit", params: &api.StrategyParameters{NodeFit: true}}, - {name: "validate params with ThresholdPriority", params: &api.StrategyParameters{ThresholdPriority: &thresholdPriority}}, - {name: "validate params with priorityClassName", params: &api.StrategyParameters{ThresholdPriorityClassName: "high-priority"}}, - {name: "validate params with excluded namespace", params: &api.StrategyParameters{Namespaces: &api.Namespaces{Exclude: []string{"excluded-ns"}}}}, - {name: "validate params with included namespace", params: &api.StrategyParameters{Namespaces: &api.Namespaces{Include: []string{"include-ns"}}}}, - {name: "validate params with empty label selector", params: &api.StrategyParameters{LabelSelector: &metav1.LabelSelector{}}}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - params, err := ValidateAndParseStrategyParams(ctx, fakeClient, tc.params) - if err != nil { - t.Errorf("strategy params should be valid but got err: %v", err.Error()) - } - if params == nil { - t.Errorf("strategy params should return a strategyParams but got nil") - } - }) - } -} - -func TestInvalidStrategyParams(t *testing.T) { - ctx := context.Background() - fakeClient := &fake.Clientset{} - testCases := []struct { - name string - params *api.StrategyParameters - }{ - { - name: "invalid params with both included and excluded namespaces nil params", - params: &api.StrategyParameters{Namespaces: &api.Namespaces{Include: []string{"include-ns"}, Exclude: []string{"exclude-ns"}}}, - }, - { - name: "invalid params with both threshold priority and priority class name", - params: &api.StrategyParameters{ThresholdPriorityClassName: "high-priority", ThresholdPriority: &thresholdPriority}, - }, - { - name: "invalid params with bad label selector", - params: &api.StrategyParameters{LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"": "missing-label"}}}, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - params, err := ValidateAndParseStrategyParams(ctx, fakeClient, tc.params) - if err == nil { - t.Errorf("strategy params should be invalid but did not get err") - } - if params != nil { - t.Errorf("strategy params should return a nil strategyParams but got %v", params) - } - }) - } -} diff --git a/pkg/descheduler/strategy_migration.go b/pkg/descheduler/strategy_migration.go index ccba80365..64e98e2d7 100644 --- a/pkg/descheduler/strategy_migration.go +++ b/pkg/descheduler/strategy_migration.go @@ -17,11 +17,12 @@ limitations under the License. package descheduler import ( - "context" + "fmt" - v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" "sigs.k8s.io/descheduler/pkg/api" + "sigs.k8s.io/descheduler/pkg/api/v1alpha1" "sigs.k8s.io/descheduler/pkg/framework" "sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization" "sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime" @@ -38,35 +39,30 @@ import ( // without any wiring. Keeping the wiring here so the descheduler can still use // the v1alpha1 configuration during the strategy migration to plugins. -var pluginsMap = map[string]func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl){ - "RemovePodsViolatingNodeTaints": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { +var strategyParamsToPluginArgs = map[string]func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error){ + "RemovePodsViolatingNodeTaints": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { args := &removepodsviolatingnodetaints.RemovePodsViolatingNodeTaintsArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, IncludePreferNoSchedule: params.IncludePreferNoSchedule, ExcludedTaints: params.ExcludedTaints, } if err := removepodsviolatingnodetaints.ValidateRemovePodsViolatingNodeTaintsArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingnodetaints.PluginName) - return - } - pg, err := removepodsviolatingnodetaints.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatingnodetaints.PluginName) - return - } - status := pg.(framework.DeschedulePlugin).Deschedule(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removepodsviolatingnodetaints.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingnodetaints.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removepodsviolatingnodetaints.PluginName, err) } + return &api.PluginConfig{ + Name: removepodsviolatingnodetaints.PluginName, + Args: args, + }, nil }, - "RemoveFailedPods": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "RemoveFailedPods": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { failedPodsParams := params.FailedPods if failedPodsParams == nil { - failedPodsParams = &api.FailedPods{} + failedPodsParams = &v1alpha1.FailedPods{} } args := &removefailedpods.RemoveFailedPodsArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, IncludingInitContainers: failedPodsParams.IncludingInitContainers, MinPodLifetimeSeconds: failedPodsParams.MinPodLifetimeSeconds, @@ -74,87 +70,67 @@ var pluginsMap = map[string]func(ctx context.Context, nodes []*v1.Node, params * Reasons: failedPodsParams.Reasons, } if err := removefailedpods.ValidateRemoveFailedPodsArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removefailedpods.PluginName) - return - } - pg, err := removefailedpods.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removefailedpods.PluginName) - return - } - status := pg.(framework.DeschedulePlugin).Deschedule(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removefailedpods.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removefailedpods.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removefailedpods.PluginName, err) } + return &api.PluginConfig{ + Name: removefailedpods.PluginName, + Args: args, + }, nil }, - "RemovePodsViolatingNodeAffinity": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "RemovePodsViolatingNodeAffinity": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { args := &removepodsviolatingnodeaffinity.RemovePodsViolatingNodeAffinityArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, NodeAffinityType: params.NodeAffinityType, } if err := removepodsviolatingnodeaffinity.ValidateRemovePodsViolatingNodeAffinityArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingnodeaffinity.PluginName) - return - } - pg, err := removepodsviolatingnodeaffinity.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatingnodeaffinity.PluginName) - return - } - status := pg.(framework.DeschedulePlugin).Deschedule(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removepodsviolatingnodeaffinity.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingnodeaffinity.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removepodsviolatingnodeaffinity.PluginName, err) } + return &api.PluginConfig{ + Name: removepodsviolatingnodeaffinity.PluginName, + Args: args, + }, nil }, - "RemovePodsViolatingInterPodAntiAffinity": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "RemovePodsViolatingInterPodAntiAffinity": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { args := &removepodsviolatinginterpodantiaffinity.RemovePodsViolatingInterPodAntiAffinityArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, } if err := removepodsviolatinginterpodantiaffinity.ValidateRemovePodsViolatingInterPodAntiAffinityArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatinginterpodantiaffinity.PluginName) - return - } - pg, err := removepodsviolatinginterpodantiaffinity.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatinginterpodantiaffinity.PluginName) - return - } - status := pg.(framework.DeschedulePlugin).Deschedule(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removepodsviolatinginterpodantiaffinity.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatinginterpodantiaffinity.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removepodsviolatinginterpodantiaffinity.PluginName, err) } + return &api.PluginConfig{ + Name: removepodsviolatinginterpodantiaffinity.PluginName, + Args: args, + }, nil }, - "RemovePodsHavingTooManyRestarts": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "RemovePodsHavingTooManyRestarts": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { tooManyRestartsParams := params.PodsHavingTooManyRestarts if tooManyRestartsParams == nil { - tooManyRestartsParams = &api.PodsHavingTooManyRestarts{} + tooManyRestartsParams = &v1alpha1.PodsHavingTooManyRestarts{} } args := &removepodshavingtoomanyrestarts.RemovePodsHavingTooManyRestartsArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, PodRestartThreshold: tooManyRestartsParams.PodRestartThreshold, IncludingInitContainers: tooManyRestartsParams.IncludingInitContainers, } if err := removepodshavingtoomanyrestarts.ValidateRemovePodsHavingTooManyRestartsArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodshavingtoomanyrestarts.PluginName) - return - } - pg, err := removepodshavingtoomanyrestarts.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removepodshavingtoomanyrestarts.PluginName) - return - } - status := pg.(framework.DeschedulePlugin).Deschedule(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removepodshavingtoomanyrestarts.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodshavingtoomanyrestarts.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removepodshavingtoomanyrestarts.PluginName, err) } + return &api.PluginConfig{ + Name: removepodshavingtoomanyrestarts.PluginName, + Args: args, + }, nil }, - "PodLifeTime": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "PodLifeTime": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { podLifeTimeParams := params.PodLifeTime if podLifeTimeParams == nil { - podLifeTimeParams = &api.PodLifeTime{} + podLifeTimeParams = &v1alpha1.PodLifeTime{} } var states []string @@ -166,106 +142,214 @@ var pluginsMap = map[string]func(ctx context.Context, nodes []*v1.Node, params * } args := &podlifetime.PodLifeTimeArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, MaxPodLifeTimeSeconds: podLifeTimeParams.MaxPodLifeTimeSeconds, States: states, } if err := podlifetime.ValidatePodLifeTimeArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", podlifetime.PluginName) - return - } - pg, err := podlifetime.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", podlifetime.PluginName) - return - } - status := pg.(framework.DeschedulePlugin).Deschedule(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", podlifetime.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", podlifetime.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", podlifetime.PluginName, err) } + return &api.PluginConfig{ + Name: podlifetime.PluginName, + Args: args, + }, nil }, - "RemoveDuplicates": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "RemoveDuplicates": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { args := &removeduplicates.RemoveDuplicatesArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), } if params.RemoveDuplicates != nil { args.ExcludeOwnerKinds = params.RemoveDuplicates.ExcludeOwnerKinds } if err := removeduplicates.ValidateRemoveDuplicatesArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removeduplicates.PluginName) - return - } - pg, err := removeduplicates.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removeduplicates.PluginName) - return - } - status := pg.(framework.BalancePlugin).Balance(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removeduplicates.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removeduplicates.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removeduplicates.PluginName, err) } + return &api.PluginConfig{ + Name: removeduplicates.PluginName, + Args: args, + }, nil }, - "RemovePodsViolatingTopologySpreadConstraint": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "RemovePodsViolatingTopologySpreadConstraint": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { args := &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{ - Namespaces: params.Namespaces, + Namespaces: v1alpha1NamespacesToInternal(params.Namespaces), LabelSelector: params.LabelSelector, IncludeSoftConstraints: params.IncludeSoftConstraints, } if err := removepodsviolatingtopologyspreadconstraint.ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName) - return - } - pg, err := removepodsviolatingtopologyspreadconstraint.New(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName) - return - } - status := pg.(framework.BalancePlugin).Balance(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", removepodsviolatingtopologyspreadconstraint.PluginName, err) } + return &api.PluginConfig{ + Name: removepodsviolatingtopologyspreadconstraint.PluginName, + Args: args, + }, nil }, - "HighNodeUtilization": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "HighNodeUtilization": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { + if params.NodeResourceUtilizationThresholds == nil { + params.NodeResourceUtilizationThresholds = &v1alpha1.NodeResourceUtilizationThresholds{} + } args := &nodeutilization.HighNodeUtilizationArgs{ - Thresholds: params.NodeResourceUtilizationThresholds.Thresholds, - NumberOfNodes: params.NodeResourceUtilizationThresholds.NumberOfNodes, + EvictableNamespaces: v1alpha1NamespacesToInternal(params.Namespaces), + Thresholds: v1alpha1ThresholdToInternal(params.NodeResourceUtilizationThresholds.Thresholds), + NumberOfNodes: params.NodeResourceUtilizationThresholds.NumberOfNodes, } - if err := nodeutilization.ValidateHighNodeUtilizationArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", nodeutilization.HighNodeUtilizationPluginName) - return - } - pg, err := nodeutilization.NewHighNodeUtilization(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", nodeutilization.HighNodeUtilizationPluginName) - return - } - status := pg.(framework.BalancePlugin).Balance(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", nodeutilization.HighNodeUtilizationPluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", nodeutilization.HighNodeUtilizationPluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", nodeutilization.HighNodeUtilizationPluginName, err) } + return &api.PluginConfig{ + Name: nodeutilization.HighNodeUtilizationPluginName, + Args: args, + }, nil }, - "LowNodeUtilization": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) { + "LowNodeUtilization": func(params *v1alpha1.StrategyParameters) (*api.PluginConfig, error) { + if params.NodeResourceUtilizationThresholds == nil { + params.NodeResourceUtilizationThresholds = &v1alpha1.NodeResourceUtilizationThresholds{} + } args := &nodeutilization.LowNodeUtilizationArgs{ - Thresholds: params.NodeResourceUtilizationThresholds.Thresholds, - TargetThresholds: params.NodeResourceUtilizationThresholds.TargetThresholds, + EvictableNamespaces: v1alpha1NamespacesToInternal(params.Namespaces), + Thresholds: v1alpha1ThresholdToInternal(params.NodeResourceUtilizationThresholds.Thresholds), + TargetThresholds: v1alpha1ThresholdToInternal(params.NodeResourceUtilizationThresholds.TargetThresholds), UseDeviationThresholds: params.NodeResourceUtilizationThresholds.UseDeviationThresholds, NumberOfNodes: params.NodeResourceUtilizationThresholds.NumberOfNodes, } if err := nodeutilization.ValidateLowNodeUtilizationArgs(args); err != nil { - klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", nodeutilization.LowNodeUtilizationPluginName) - return - } - pg, err := nodeutilization.NewLowNodeUtilization(args, handle) - if err != nil { - klog.V(1).ErrorS(err, "unable to initialize a plugin", "pluginName", nodeutilization.LowNodeUtilizationPluginName) - return - } - status := pg.(framework.BalancePlugin).Balance(ctx, nodes) - if status != nil && status.Err != nil { - klog.V(1).ErrorS(err, "plugin finished with error", "pluginName", nodeutilization.LowNodeUtilizationPluginName) + klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", nodeutilization.LowNodeUtilizationPluginName) + return nil, fmt.Errorf("strategy %q param validation failed: %v", nodeutilization.LowNodeUtilizationPluginName, err) } + return &api.PluginConfig{ + Name: nodeutilization.LowNodeUtilizationPluginName, + Args: args, + }, nil + }, +} + +func v1alpha1NamespacesToInternal(namespaces *v1alpha1.Namespaces) *api.Namespaces { + internal := &api.Namespaces{} + if namespaces != nil { + if namespaces.Exclude != nil { + internal.Exclude = namespaces.Exclude + } + if namespaces.Include != nil { + internal.Include = namespaces.Include + } + } else { + internal = nil + } + return internal +} + +func v1alpha1ThresholdToInternal(thresholds v1alpha1.ResourceThresholds) api.ResourceThresholds { + internal := make(api.ResourceThresholds, len(thresholds)) + for k, v := range thresholds { + internal[k] = api.Percentage(float64(v)) + } + return internal +} + +type extensionPoint string + +const ( + descheduleEP extensionPoint = "deschedule" + balanceEP extensionPoint = "balance" +) + +var pluginToExtensionPoint = map[string]extensionPoint{ + removepodsviolatingnodetaints.PluginName: descheduleEP, + removefailedpods.PluginName: descheduleEP, + removepodsviolatingnodeaffinity.PluginName: descheduleEP, + removepodsviolatinginterpodantiaffinity.PluginName: descheduleEP, + removepodshavingtoomanyrestarts.PluginName: descheduleEP, + podlifetime.PluginName: descheduleEP, + removeduplicates.PluginName: balanceEP, + removepodsviolatingtopologyspreadconstraint.PluginName: balanceEP, + nodeutilization.HighNodeUtilizationPluginName: balanceEP, + nodeutilization.LowNodeUtilizationPluginName: balanceEP, +} + +var pluginsMap = map[string]func(args runtime.Object, handle *handleImpl) framework.Plugin{ + removepodsviolatingnodetaints.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removepodsviolatingnodetaints.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatingnodetaints.PluginName) + return nil + } + return pg + }, + removefailedpods.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removefailedpods.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removefailedpods.PluginName) + return nil + } + return pg + }, + removepodsviolatingnodeaffinity.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removepodsviolatingnodeaffinity.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatingnodeaffinity.PluginName) + return nil + } + return pg + }, + removepodsviolatinginterpodantiaffinity.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removepodsviolatinginterpodantiaffinity.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatinginterpodantiaffinity.PluginName) + return nil + } + return pg + }, + removepodshavingtoomanyrestarts.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removepodshavingtoomanyrestarts.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removepodshavingtoomanyrestarts.PluginName) + return nil + } + return pg + }, + podlifetime.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := podlifetime.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", podlifetime.PluginName) + return nil + } + return pg + }, + removeduplicates.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removeduplicates.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removeduplicates.PluginName) + return nil + } + return pg + }, + removepodsviolatingtopologyspreadconstraint.PluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := removepodsviolatingtopologyspreadconstraint.New(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName) + return nil + } + return pg + }, + nodeutilization.HighNodeUtilizationPluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := nodeutilization.NewHighNodeUtilization(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", nodeutilization.HighNodeUtilizationPluginName) + return nil + } + return pg + }, + nodeutilization.LowNodeUtilizationPluginName: func(args runtime.Object, handle *handleImpl) framework.Plugin { + pg, err := nodeutilization.NewLowNodeUtilization(args, handle) + if err != nil { + klog.ErrorS(err, "unable to initialize a plugin", "pluginName", nodeutilization.LowNodeUtilizationPluginName) + return nil + } + return pg }, } diff --git a/pkg/descheduler/strategy_migration_test.go b/pkg/descheduler/strategy_migration_test.go new file mode 100644 index 000000000..da342c3d2 --- /dev/null +++ b/pkg/descheduler/strategy_migration_test.go @@ -0,0 +1,844 @@ +/* +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 descheduler + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + utilpointer "k8s.io/utils/pointer" + "sigs.k8s.io/descheduler/pkg/api" + "sigs.k8s.io/descheduler/pkg/api/v1alpha1" + "sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization" + "sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removeduplicates" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removefailedpods" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodshavingtoomanyrestarts" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatinginterpodantiaffinity" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatingnodeaffinity" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatingnodetaints" + "sigs.k8s.io/descheduler/pkg/framework/plugins/removepodsviolatingtopologyspreadconstraint" +) + +func TestStrategyParamsToPluginArgsRemovePodsViolatingNodeTaints(t *testing.T) { + strategyName := "RemovePodsViolatingNodeTaints" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + ExcludedTaints: []string{ + "dedicated=special-user", + "reserved", + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removepodsviolatingnodetaints.PluginName, + Args: &removepodsviolatingnodetaints.RemovePodsViolatingNodeTaintsArgs{ + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + ExcludedTaints: []string{"dedicated=special-user", "reserved"}, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsRemoveFailedPods(t *testing.T) { + strategyName := "RemoveFailedPods" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + FailedPods: &v1alpha1.FailedPods{ + MinPodLifetimeSeconds: utilpointer.Uint(3600), + ExcludeOwnerKinds: []string{"Job"}, + Reasons: []string{"NodeAffinity"}, + IncludingInitContainers: true, + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removefailedpods.PluginName, + Args: &removefailedpods.RemoveFailedPodsArgs{ + ExcludeOwnerKinds: []string{"Job"}, + MinPodLifetimeSeconds: utilpointer.Uint(3600), + Reasons: []string{"NodeAffinity"}, + IncludingInitContainers: true, + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsRemovePodsViolatingNodeAffinity(t *testing.T) { + strategyName := "RemovePodsViolatingNodeAffinity" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removepodsviolatingnodeaffinity.PluginName, + Args: &removepodsviolatingnodeaffinity.RemovePodsViolatingNodeAffinityArgs{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params, not setting nodeaffinity type", + params: &v1alpha1.StrategyParameters{}, + err: fmt.Errorf("strategy \"%s\" param validation failed: nodeAffinityType needs to be set", strategyName), + result: nil, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"}, + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsRemovePodsViolatingInterPodAntiAffinity(t *testing.T) { + strategyName := "RemovePodsViolatingInterPodAntiAffinity" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removepodsviolatinginterpodantiaffinity.PluginName, + Args: &removepodsviolatinginterpodantiaffinity.RemovePodsViolatingInterPodAntiAffinityArgs{ + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsRemovePodsHavingTooManyRestarts(t *testing.T) { + strategyName := "RemovePodsHavingTooManyRestarts" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + PodsHavingTooManyRestarts: &v1alpha1.PodsHavingTooManyRestarts{ + PodRestartThreshold: 100, + IncludingInitContainers: true, + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removepodshavingtoomanyrestarts.PluginName, + Args: &removepodshavingtoomanyrestarts.RemovePodsHavingTooManyRestartsArgs{ + PodRestartThreshold: 100, + IncludingInitContainers: true, + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + { + description: "invalid params restart threshold", + params: &v1alpha1.StrategyParameters{ + PodsHavingTooManyRestarts: &v1alpha1.PodsHavingTooManyRestarts{ + PodRestartThreshold: 0, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: invalid PodsHavingTooManyRestarts threshold", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsPodLifeTime(t *testing.T) { + strategyName := "PodLifeTime" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + PodLifeTime: &v1alpha1.PodLifeTime{ + MaxPodLifeTimeSeconds: utilpointer.Uint(86400), + States: []string{ + "Pending", + "PodInitializing", + }, + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: podlifetime.PluginName, + Args: &podlifetime.PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: utilpointer.Uint(86400), + States: []string{ + "Pending", + "PodInitializing", + }, + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + PodLifeTime: &v1alpha1.PodLifeTime{ + MaxPodLifeTimeSeconds: utilpointer.Uint(86400), + }, + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + { + description: "invalid params MaxPodLifeTimeSeconds not set", + params: &v1alpha1.StrategyParameters{ + PodLifeTime: &v1alpha1.PodLifeTime{}, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: MaxPodLifeTimeSeconds not set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsRemoveDuplicates(t *testing.T) { + strategyName := "RemoveDuplicates" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + RemoveDuplicates: &v1alpha1.RemoveDuplicates{ + ExcludeOwnerKinds: []string{"ReplicaSet"}, + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removeduplicates.PluginName, + Args: &removeduplicates.RemoveDuplicatesArgs{ + ExcludeOwnerKinds: []string{"ReplicaSet"}, + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + PodLifeTime: &v1alpha1.PodLifeTime{ + MaxPodLifeTimeSeconds: utilpointer.Uint(86400), + }, + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsRemovePodsViolatingTopologySpreadConstraint(t *testing.T) { + strategyName := "RemovePodsViolatingTopologySpreadConstraint" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + IncludeSoftConstraints: true, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: removepodsviolatingtopologyspreadconstraint.PluginName, + Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{ + IncludeSoftConstraints: true, + Namespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only one of Include/Exclude namespaces can be set", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsHighNodeUtilization(t *testing.T) { + strategyName := "HighNodeUtilization" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: nodeutilization.HighNodeUtilizationPluginName, + Args: &nodeutilization.HighNodeUtilizationArgs{ + Thresholds: api.ResourceThresholds{ + "cpu": api.Percentage(20), + "memory": api.Percentage(20), + "pods": api.Percentage(20), + }, + NumberOfNodes: 3, + EvictableNamespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + }, + Namespaces: &v1alpha1.Namespaces{ + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only Exclude namespaces can be set, inclusion is not supported", strategyName), + result: nil, + }, + { + description: "invalid params nil ResourceThresholds", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: no resource threshold is configured", strategyName), + result: nil, + }, + { + description: "invalid params out of bounds threshold", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(150), + }, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: cpu threshold not in [0, 100] range", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} + +func TestStrategyParamsToPluginArgsLowNodeUtilization(t *testing.T) { + strategyName := "LowNodeUtilization" + type testCase struct { + description string + params *v1alpha1.StrategyParameters + err error + result *api.PluginConfig + } + testCases := []testCase{ + { + description: "wire in all valid parameters", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + TargetThresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(50), + "memory": v1alpha1.Percentage(50), + "pods": v1alpha1.Percentage(50), + }, + UseDeviationThresholds: true, + }, + ThresholdPriority: utilpointer.Int32(100), + Namespaces: &v1alpha1.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + err: nil, + result: &api.PluginConfig{ + Name: nodeutilization.LowNodeUtilizationPluginName, + Args: &nodeutilization.LowNodeUtilizationArgs{ + Thresholds: api.ResourceThresholds{ + "cpu": api.Percentage(20), + "memory": api.Percentage(20), + "pods": api.Percentage(20), + }, + TargetThresholds: api.ResourceThresholds{ + "cpu": api.Percentage(50), + "memory": api.Percentage(50), + "pods": api.Percentage(50), + }, + UseDeviationThresholds: true, + NumberOfNodes: 3, + EvictableNamespaces: &api.Namespaces{ + Exclude: []string{"test1"}, + }, + }, + }, + }, + { + description: "invalid params namespaces", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(20), + "memory": v1alpha1.Percentage(20), + "pods": v1alpha1.Percentage(20), + }, + TargetThresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(50), + "memory": v1alpha1.Percentage(50), + "pods": v1alpha1.Percentage(50), + }, + UseDeviationThresholds: true, + }, + Namespaces: &v1alpha1.Namespaces{ + Include: []string{"test2"}, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: only Exclude namespaces can be set, inclusion is not supported", strategyName), + result: nil, + }, + { + description: "invalid params nil ResourceThresholds", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: thresholds config is not valid: no resource threshold is configured", strategyName), + result: nil, + }, + { + description: "invalid params out of bounds threshold", + params: &v1alpha1.StrategyParameters{ + NodeResourceUtilizationThresholds: &v1alpha1.NodeResourceUtilizationThresholds{ + NumberOfNodes: 3, + Thresholds: v1alpha1.ResourceThresholds{ + "cpu": v1alpha1.Percentage(150), + }, + }, + }, + err: fmt.Errorf("strategy \"%s\" param validation failed: thresholds config is not valid: cpu threshold not in [0, 100] range", strategyName), + result: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var result *api.PluginConfig + var err error + if pcFnc, exists := strategyParamsToPluginArgs[strategyName]; exists { + result, err = pcFnc(tc.params) + } + if err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("unexpected error: %s", err.Error()) + } + } + if err == nil { + // sort to easily compare deepequality + diff := cmp.Diff(tc.result, result) + if diff != "" { + t.Errorf("test '%s' failed. Results are not deep equal. mismatch (-want +got):\n%s", tc.description, diff) + } + } + }) + } +} diff --git a/pkg/framework/plugins/defaultevictor/defaultevictor.go b/pkg/framework/plugins/defaultevictor/defaultevictor.go index ee2e7df60..19c4ade82 100644 --- a/pkg/framework/plugins/defaultevictor/defaultevictor.go +++ b/pkg/framework/plugins/defaultevictor/defaultevictor.go @@ -151,11 +151,11 @@ func (d *DefaultEvictor) PreEvictionFilter(pod *v1.Pod) bool { if defaultEvictorArgs.NodeFit { nodes, err := nodeutil.ReadyNodes(context.TODO(), d.handle.ClientSet(), d.handle.SharedInformerFactory().Core().V1().Nodes().Lister(), defaultEvictorArgs.NodeSelector) if err != nil { - klog.V(1).ErrorS(fmt.Errorf("Pod fails the following checks"), "pod", klog.KObj(pod)) + klog.ErrorS(fmt.Errorf("Pod fails the following checks"), "pod", klog.KObj(pod)) return false } if !nodeutil.PodFitsAnyOtherNode(d.handle.GetPodsAssignedToNodeFunc(), pod, nodes) { - klog.V(1).ErrorS(fmt.Errorf("pod does not fit on any other node because of nodeSelector(s), Taint(s), or nodes marked as unschedulable"), "pod", klog.KObj(pod)) + klog.ErrorS(fmt.Errorf("pod does not fit on any other node because of nodeSelector(s), Taint(s), or nodes marked as unschedulable"), "pod", klog.KObj(pod)) return false } return true diff --git a/pkg/framework/plugins/nodeutilization/nodeutilization.go b/pkg/framework/plugins/nodeutilization/nodeutilization.go index 846ec567c..053f7c208 100644 --- a/pkg/framework/plugins/nodeutilization/nodeutilization.go +++ b/pkg/framework/plugins/nodeutilization/nodeutilization.go @@ -299,7 +299,7 @@ func evictPods( WithoutNamespaces(excludedNamespaces). BuildFilterFunc() if err != nil { - klog.V(1).ErrorS(err, "could not build preEvictionFilter with namespace exclusion") + klog.ErrorS(err, "could not build preEvictionFilter with namespace exclusion") continue } diff --git a/pkg/framework/plugins/nodeutilization/validation.go b/pkg/framework/plugins/nodeutilization/validation.go index 754f80e74..ab7d450e1 100644 --- a/pkg/framework/plugins/nodeutilization/validation.go +++ b/pkg/framework/plugins/nodeutilization/validation.go @@ -33,7 +33,15 @@ func ValidateHighNodeUtilizationArgs(args *HighNodeUtilizationArgs) error { } func ValidateLowNodeUtilizationArgs(args *LowNodeUtilizationArgs) error { - return validateLowNodeUtilizationThresholds(args.Thresholds, args.TargetThresholds, args.UseDeviationThresholds) + // only exclude can be set, or not at all + if args.EvictableNamespaces != nil && len(args.EvictableNamespaces.Include) > 0 { + return fmt.Errorf("only Exclude namespaces can be set, inclusion is not supported") + } + err := validateLowNodeUtilizationThresholds(args.Thresholds, args.TargetThresholds, args.UseDeviationThresholds) + if err != nil { + return err + } + return nil } func validateLowNodeUtilizationThresholds(thresholds, targetThresholds api.ResourceThresholds, useDeviationThresholds bool) error { diff --git a/pkg/framework/plugins/removepodshavingtoomanyrestarts/validation.go b/pkg/framework/plugins/removepodshavingtoomanyrestarts/validation.go index 03e3fd453..cfb736532 100644 --- a/pkg/framework/plugins/removepodshavingtoomanyrestarts/validation.go +++ b/pkg/framework/plugins/removepodshavingtoomanyrestarts/validation.go @@ -33,7 +33,7 @@ func ValidateRemovePodsHavingTooManyRestartsArgs(args *RemovePodsHavingTooManyRe } if args.PodRestartThreshold < 1 { - return fmt.Errorf("PodsHavingTooManyRestarts threshold not set") + return fmt.Errorf("invalid PodsHavingTooManyRestarts threshold") } return nil diff --git a/pkg/utils/priority.go b/pkg/utils/priority.go index 058de5ea8..92a140ad3 100644 --- a/pkg/utils/priority.go +++ b/pkg/utils/priority.go @@ -55,14 +55,14 @@ func GetPriorityFromPriorityClass(ctx context.Context, client clientset.Interfac // GetPriorityFromStrategyParams gets priority from the given StrategyParameters. // It will return SystemCriticalPriority by default. -func GetPriorityFromStrategyParams(ctx context.Context, client clientset.Interface, params *api.StrategyParameters) (priority int32, err error) { - if params == nil { +func GetPriorityFromStrategyParams(ctx context.Context, client clientset.Interface, priorityThreshold *api.PriorityThreshold) (priority int32, err error) { + if priorityThreshold == nil { return SystemCriticalPriority, nil } - if params.ThresholdPriority != nil { - priority = *params.ThresholdPriority + if priorityThreshold.Value != nil { + priority = *priorityThreshold.Value } else { - priority, err = GetPriorityFromPriorityClass(ctx, client, params.ThresholdPriorityClassName) + priority, err = GetPriorityFromPriorityClass(ctx, client, priorityThreshold.Name) if err != nil { return }