mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-26 05:14:13 +01:00
feature(eviction): add event when EvictPod failed
This commit is contained in:
@@ -42,6 +42,10 @@ type DeschedulerPolicy struct {
|
|||||||
// MaxNoOfPodsToTotal restricts maximum of pods to be evicted total.
|
// MaxNoOfPodsToTotal restricts maximum of pods to be evicted total.
|
||||||
MaxNoOfPodsToEvictTotal *uint
|
MaxNoOfPodsToEvictTotal *uint
|
||||||
|
|
||||||
|
// EvictionFailureEventNotification should be set to true to enable eviction failure event notification.
|
||||||
|
// Default is false.
|
||||||
|
EvictionFailureEventNotification *bool
|
||||||
|
|
||||||
// MetricsCollector configures collection of metrics about actual resource utilization
|
// MetricsCollector configures collection of metrics about actual resource utilization
|
||||||
MetricsCollector MetricsCollector
|
MetricsCollector MetricsCollector
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ type DeschedulerPolicy struct {
|
|||||||
// MaxNoOfPodsToTotal restricts maximum of pods to be evicted total.
|
// MaxNoOfPodsToTotal restricts maximum of pods to be evicted total.
|
||||||
MaxNoOfPodsToEvictTotal *uint `json:"maxNoOfPodsToEvictTotal,omitempty"`
|
MaxNoOfPodsToEvictTotal *uint `json:"maxNoOfPodsToEvictTotal,omitempty"`
|
||||||
|
|
||||||
|
// EvictionFailureEventNotification should be set to true to enable eviction failure event notification.
|
||||||
|
// Default is false.
|
||||||
|
EvictionFailureEventNotification *bool
|
||||||
|
|
||||||
// MetricsCollector configures collection of metrics for actual resource utilization
|
// MetricsCollector configures collection of metrics for actual resource utilization
|
||||||
MetricsCollector MetricsCollector `json:"metricsCollector,omitempty"`
|
MetricsCollector MetricsCollector `json:"metricsCollector,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
2
pkg/api/v1alpha2/zz_generated.conversion.go
generated
2
pkg/api/v1alpha2/zz_generated.conversion.go
generated
@@ -115,6 +115,7 @@ func autoConvert_v1alpha2_DeschedulerPolicy_To_api_DeschedulerPolicy(in *Desched
|
|||||||
out.MaxNoOfPodsToEvictPerNode = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNode))
|
out.MaxNoOfPodsToEvictPerNode = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNode))
|
||||||
out.MaxNoOfPodsToEvictPerNamespace = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNamespace))
|
out.MaxNoOfPodsToEvictPerNamespace = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNamespace))
|
||||||
out.MaxNoOfPodsToEvictTotal = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictTotal))
|
out.MaxNoOfPodsToEvictTotal = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictTotal))
|
||||||
|
out.EvictionFailureEventNotification = (*bool)(unsafe.Pointer(in.EvictionFailureEventNotification))
|
||||||
if err := Convert_v1alpha2_MetricsCollector_To_api_MetricsCollector(&in.MetricsCollector, &out.MetricsCollector, s); err != nil {
|
if err := Convert_v1alpha2_MetricsCollector_To_api_MetricsCollector(&in.MetricsCollector, &out.MetricsCollector, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -137,6 +138,7 @@ func autoConvert_api_DeschedulerPolicy_To_v1alpha2_DeschedulerPolicy(in *api.Des
|
|||||||
out.MaxNoOfPodsToEvictPerNode = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNode))
|
out.MaxNoOfPodsToEvictPerNode = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNode))
|
||||||
out.MaxNoOfPodsToEvictPerNamespace = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNamespace))
|
out.MaxNoOfPodsToEvictPerNamespace = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNamespace))
|
||||||
out.MaxNoOfPodsToEvictTotal = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictTotal))
|
out.MaxNoOfPodsToEvictTotal = (*uint)(unsafe.Pointer(in.MaxNoOfPodsToEvictTotal))
|
||||||
|
out.EvictionFailureEventNotification = (*bool)(unsafe.Pointer(in.EvictionFailureEventNotification))
|
||||||
if err := Convert_api_MetricsCollector_To_v1alpha2_MetricsCollector(&in.MetricsCollector, &out.MetricsCollector, s); err != nil {
|
if err := Convert_api_MetricsCollector_To_v1alpha2_MetricsCollector(&in.MetricsCollector, &out.MetricsCollector, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
5
pkg/api/v1alpha2/zz_generated.deepcopy.go
generated
5
pkg/api/v1alpha2/zz_generated.deepcopy.go
generated
@@ -56,6 +56,11 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) {
|
|||||||
*out = new(uint)
|
*out = new(uint)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.EvictionFailureEventNotification != nil {
|
||||||
|
in, out := &in.EvictionFailureEventNotification, &out.EvictionFailureEventNotification
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
out.MetricsCollector = in.MetricsCollector
|
out.MetricsCollector = in.MetricsCollector
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
5
pkg/api/zz_generated.deepcopy.go
generated
5
pkg/api/zz_generated.deepcopy.go
generated
@@ -56,6 +56,11 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) {
|
|||||||
*out = new(uint)
|
*out = new(uint)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.EvictionFailureEventNotification != nil {
|
||||||
|
in, out := &in.EvictionFailureEventNotification, &out.EvictionFailureEventNotification
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
out.MetricsCollector = in.MetricsCollector
|
out.MetricsCollector = in.MetricsCollector
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ func newDescheduler(ctx context.Context, rs *options.DeschedulerServer, deschedu
|
|||||||
WithMaxPodsToEvictPerNode(deschedulerPolicy.MaxNoOfPodsToEvictPerNode).
|
WithMaxPodsToEvictPerNode(deschedulerPolicy.MaxNoOfPodsToEvictPerNode).
|
||||||
WithMaxPodsToEvictPerNamespace(deschedulerPolicy.MaxNoOfPodsToEvictPerNamespace).
|
WithMaxPodsToEvictPerNamespace(deschedulerPolicy.MaxNoOfPodsToEvictPerNamespace).
|
||||||
WithMaxPodsToEvictTotal(deschedulerPolicy.MaxNoOfPodsToEvictTotal).
|
WithMaxPodsToEvictTotal(deschedulerPolicy.MaxNoOfPodsToEvictTotal).
|
||||||
|
WithEvictionFailureEventNotification(deschedulerPolicy.EvictionFailureEventNotification).
|
||||||
WithDryRun(rs.DryRun).
|
WithDryRun(rs.DryRun).
|
||||||
WithMetricsEnabled(!rs.DisableMetrics),
|
WithMetricsEnabled(!rs.DisableMetrics),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -206,20 +206,21 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PodEvictor struct {
|
type PodEvictor struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
client clientset.Interface
|
client clientset.Interface
|
||||||
policyGroupVersion string
|
policyGroupVersion string
|
||||||
dryRun bool
|
dryRun bool
|
||||||
maxPodsToEvictPerNode *uint
|
evictionFailureEventNotification bool
|
||||||
maxPodsToEvictPerNamespace *uint
|
maxPodsToEvictPerNode *uint
|
||||||
maxPodsToEvictTotal *uint
|
maxPodsToEvictPerNamespace *uint
|
||||||
nodePodCount nodePodEvictedCount
|
maxPodsToEvictTotal *uint
|
||||||
namespacePodCount namespacePodEvictCount
|
nodePodCount nodePodEvictedCount
|
||||||
totalPodCount uint
|
namespacePodCount namespacePodEvictCount
|
||||||
metricsEnabled bool
|
totalPodCount uint
|
||||||
eventRecorder events.EventRecorder
|
metricsEnabled bool
|
||||||
erCache *evictionRequestsCache
|
eventRecorder events.EventRecorder
|
||||||
featureGates featuregate.FeatureGate
|
erCache *evictionRequestsCache
|
||||||
|
featureGates featuregate.FeatureGate
|
||||||
|
|
||||||
// registeredHandlers contains the registrations of all handlers. It's used to check if all handlers have finished syncing before the scheduling cycles start.
|
// registeredHandlers contains the registrations of all handlers. It's used to check if all handlers have finished syncing before the scheduling cycles start.
|
||||||
registeredHandlers []cache.ResourceEventHandlerRegistration
|
registeredHandlers []cache.ResourceEventHandlerRegistration
|
||||||
@@ -238,17 +239,18 @@ func NewPodEvictor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
podEvictor := &PodEvictor{
|
podEvictor := &PodEvictor{
|
||||||
client: client,
|
client: client,
|
||||||
eventRecorder: eventRecorder,
|
eventRecorder: eventRecorder,
|
||||||
policyGroupVersion: options.policyGroupVersion,
|
policyGroupVersion: options.policyGroupVersion,
|
||||||
dryRun: options.dryRun,
|
dryRun: options.dryRun,
|
||||||
maxPodsToEvictPerNode: options.maxPodsToEvictPerNode,
|
evictionFailureEventNotification: options.evictionFailureEventNotification,
|
||||||
maxPodsToEvictPerNamespace: options.maxPodsToEvictPerNamespace,
|
maxPodsToEvictPerNode: options.maxPodsToEvictPerNode,
|
||||||
maxPodsToEvictTotal: options.maxPodsToEvictTotal,
|
maxPodsToEvictPerNamespace: options.maxPodsToEvictPerNamespace,
|
||||||
metricsEnabled: options.metricsEnabled,
|
maxPodsToEvictTotal: options.maxPodsToEvictTotal,
|
||||||
nodePodCount: make(nodePodEvictedCount),
|
metricsEnabled: options.metricsEnabled,
|
||||||
namespacePodCount: make(namespacePodEvictCount),
|
nodePodCount: make(nodePodEvictedCount),
|
||||||
featureGates: featureGates,
|
namespacePodCount: make(namespacePodEvictCount),
|
||||||
|
featureGates: featureGates,
|
||||||
}
|
}
|
||||||
|
|
||||||
if featureGates.Enabled(features.EvictionsInBackground) {
|
if featureGates.Enabled(features.EvictionsInBackground) {
|
||||||
@@ -481,6 +483,9 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
|
|||||||
}
|
}
|
||||||
span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error())))
|
span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error())))
|
||||||
klog.ErrorS(err, "Error evicting pod", "limit", *pe.maxPodsToEvictTotal)
|
klog.ErrorS(err, "Error evicting pod", "limit", *pe.maxPodsToEvictTotal)
|
||||||
|
if pe.evictionFailureEventNotification {
|
||||||
|
pe.eventRecorder.Eventf(pod, nil, v1.EventTypeWarning, "EvictionFailed", "Descheduled", "pod eviction from %v node by sigs.k8s.io/descheduler failed: total eviction limit exceeded (%v)", pod.Spec.NodeName, *pe.maxPodsToEvictTotal)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,6 +497,9 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
|
|||||||
}
|
}
|
||||||
span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error())))
|
span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error())))
|
||||||
klog.ErrorS(err, "Error evicting pod", "limit", *pe.maxPodsToEvictPerNode, "node", pod.Spec.NodeName)
|
klog.ErrorS(err, "Error evicting pod", "limit", *pe.maxPodsToEvictPerNode, "node", pod.Spec.NodeName)
|
||||||
|
if pe.evictionFailureEventNotification {
|
||||||
|
pe.eventRecorder.Eventf(pod, nil, v1.EventTypeWarning, "EvictionFailed", "Descheduled", "pod eviction from %v node by sigs.k8s.io/descheduler failed: node eviction limit exceeded (%v)", pod.Spec.NodeName, *pe.maxPodsToEvictPerNode)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,6 +511,9 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
|
|||||||
}
|
}
|
||||||
span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error())))
|
span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error())))
|
||||||
klog.ErrorS(err, "Error evicting pod", "limit", *pe.maxPodsToEvictPerNamespace, "namespace", pod.Namespace, "pod", klog.KObj(pod))
|
klog.ErrorS(err, "Error evicting pod", "limit", *pe.maxPodsToEvictPerNamespace, "namespace", pod.Namespace, "pod", klog.KObj(pod))
|
||||||
|
if pe.evictionFailureEventNotification {
|
||||||
|
pe.eventRecorder.Eventf(pod, nil, v1.EventTypeWarning, "EvictionFailed", "Descheduled", "pod eviction from %v node by sigs.k8s.io/descheduler failed: namespace eviction limit exceeded (%v)", pod.Spec.NodeName, *pe.maxPodsToEvictPerNamespace)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,6 +525,9 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
|
|||||||
if pe.metricsEnabled {
|
if pe.metricsEnabled {
|
||||||
metrics.PodsEvicted.With(map[string]string{"result": "error", "strategy": opts.StrategyName, "namespace": pod.Namespace, "node": pod.Spec.NodeName, "profile": opts.ProfileName}).Inc()
|
metrics.PodsEvicted.With(map[string]string{"result": "error", "strategy": opts.StrategyName, "namespace": pod.Namespace, "node": pod.Spec.NodeName, "profile": opts.ProfileName}).Inc()
|
||||||
}
|
}
|
||||||
|
if pe.evictionFailureEventNotification {
|
||||||
|
pe.eventRecorder.Eventf(pod, nil, v1.EventTypeWarning, "EvictionFailed", "Descheduled", "pod eviction from %v node by sigs.k8s.io/descheduler failed: %v", pod.Spec.NodeName, err.Error())
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +556,7 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
|
|||||||
reason = "NotSet"
|
reason = "NotSet"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pe.eventRecorder.Eventf(pod, nil, v1.EventTypeNormal, reason, "Descheduled", "pod evicted from %v node by sigs.k8s.io/descheduler", pod.Spec.NodeName)
|
pe.eventRecorder.Eventf(pod, nil, v1.EventTypeNormal, reason, "Descheduled", "pod eviction from %v node by sigs.k8s.io/descheduler", pod.Spec.NodeName)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package evictions
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||||
@@ -43,6 +45,11 @@ import (
|
|||||||
"sigs.k8s.io/descheduler/test"
|
"sigs.k8s.io/descheduler/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
notFoundText = "pod not found when evicting \"%s\": pods \"%s\" not found"
|
||||||
|
tooManyRequests = "error when evicting pod (ignoring) \"%s\": Too many requests: too many requests"
|
||||||
|
)
|
||||||
|
|
||||||
func initFeatureGates() featuregate.FeatureGate {
|
func initFeatureGates() featuregate.FeatureGate {
|
||||||
featureGates := featuregate.NewFeatureGate()
|
featureGates := featuregate.NewFeatureGate()
|
||||||
featureGates.Add(map[featuregate.Feature]featuregate.FeatureSpec{
|
featureGates.Add(map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
@@ -57,32 +64,38 @@ func TestEvictPod(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
description string
|
description string
|
||||||
node *v1.Node
|
node *v1.Node
|
||||||
pod *v1.Pod
|
evictedPod *v1.Pod
|
||||||
pods []v1.Pod
|
pods []runtime.Object
|
||||||
want error
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "test pod eviction - pod present",
|
description: "test pod eviction - pod present",
|
||||||
node: node1,
|
node: node1,
|
||||||
pod: pod1,
|
evictedPod: pod1,
|
||||||
pods: []v1.Pod{*pod1},
|
pods: []runtime.Object{pod1},
|
||||||
want: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "test pod eviction - pod absent",
|
description: "test pod eviction - pod absent (not found error)",
|
||||||
node: node1,
|
node: node1,
|
||||||
pod: pod1,
|
evictedPod: pod1,
|
||||||
pods: []v1.Pod{*test.BuildTestPod("p2", 400, 0, "node1", nil), *test.BuildTestPod("p3", 450, 0, "node1", nil)},
|
pods: []runtime.Object{test.BuildTestPod("p2", 400, 0, "node1", nil), test.BuildTestPod("p3", 450, 0, "node1", nil)},
|
||||||
want: nil,
|
wantErr: fmt.Errorf(notFoundText, pod1.Name, pod1.Name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "test pod eviction - pod absent (too many requests error)",
|
||||||
|
node: node1,
|
||||||
|
evictedPod: pod1,
|
||||||
|
pods: []runtime.Object{test.BuildTestPod("p2", 400, 0, "node1", nil), test.BuildTestPod("p3", 450, 0, "node1", nil)},
|
||||||
|
wantErr: fmt.Errorf(tooManyRequests, pod1.Name),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.description, func(t *testing.T) {
|
t.Run(test.description, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
fakeClient := &fake.Clientset{}
|
fakeClient := fake.NewClientset(test.pods...)
|
||||||
fakeClient.Fake.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
fakeClient.PrependReactor("create", "pods/eviction", func(action core.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, &v1.PodList{Items: test.pods}, nil
|
return true, nil, test.wantErr
|
||||||
})
|
})
|
||||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
sharedInformerFactory.Start(ctx.Done())
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
@@ -101,9 +114,9 @@ func TestEvictPod(t *testing.T) {
|
|||||||
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, got := podEvictor.evictPod(ctx, test.pod)
|
_, got := podEvictor.evictPod(ctx, test.evictedPod)
|
||||||
if got != test.want {
|
if got != test.wantErr {
|
||||||
t.Errorf("Test error for Desc: %s. Expected %v pod eviction to be %v, got %v", test.description, test.pod.Name, test.want, got)
|
t.Errorf("Test error for Desc: %s. Expected %v pod eviction to be %v, got %v", test.description, test.evictedPod.Name, test.wantErr, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -158,60 +171,205 @@ func TestNewPodEvictor(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
pod1 := test.BuildTestPod("pod", 400, 0, "node", nil)
|
pod1 := test.BuildTestPod("pod", 400, 0, "node", nil)
|
||||||
|
type podEvictorTest struct {
|
||||||
fakeClient := fake.NewSimpleClientset(pod1)
|
description string
|
||||||
|
pod *v1.Pod
|
||||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
dryRun bool
|
||||||
sharedInformerFactory.Start(ctx.Done())
|
evictionFailureEventNotification *bool
|
||||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
maxPodsToEvictTotal *uint
|
||||||
|
maxPodsToEvictPerNode *uint
|
||||||
eventRecorder := &events.FakeRecorder{}
|
maxPodsToEvictPerNamespace *uint
|
||||||
|
expectedNodeEvictions uint
|
||||||
podEvictor, err := NewPodEvictor(
|
expectedTotalEvictions uint
|
||||||
ctx,
|
expectedError error
|
||||||
fakeClient,
|
// expectedEvent is a slice of strings representing expected events.
|
||||||
eventRecorder,
|
// Each string in the slice should follow the format: "EventType Reason Message".
|
||||||
sharedInformerFactory.Core().V1().Pods().Informer(),
|
// - "Warning Failed processing failed"
|
||||||
initFeatureGates(),
|
events []string
|
||||||
NewOptions().WithMaxPodsToEvictPerNode(utilptr.To[uint](1)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
|
||||||
}
|
}
|
||||||
|
tests := []podEvictorTest{
|
||||||
|
{
|
||||||
|
description: "one eviction expected with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
evictionFailureEventNotification: utilptr.To[bool](true),
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 1,
|
||||||
|
expectedTotalEvictions: 1,
|
||||||
|
expectedError: nil,
|
||||||
|
events: []string{"Normal NotSet pod eviction from node node by sigs.k8s.io/descheduler"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction limit exceeded on total with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
evictionFailureEventNotification: utilptr.To[bool](true),
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](0),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: NewEvictionTotalLimitError(),
|
||||||
|
events: []string{"Warning EvictionFailed pod eviction from node node by sigs.k8s.io/descheduler failed: total eviction limit exceeded (0)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction limit exceeded on node with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
evictionFailureEventNotification: utilptr.To[bool](true),
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](0),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: NewEvictionNodeLimitError("node"),
|
||||||
|
events: []string{"Warning EvictionFailed pod eviction from node node by sigs.k8s.io/descheduler failed: node eviction limit exceeded (0)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction limit exceeded on node with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
evictionFailureEventNotification: utilptr.To[bool](true),
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](0),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: NewEvictionNamespaceLimitError("default"),
|
||||||
|
events: []string{"Warning EvictionFailed pod eviction from node node by sigs.k8s.io/descheduler failed: namespace eviction limit exceeded (0)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction error with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
evictionFailureEventNotification: utilptr.To[bool](true),
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: fmt.Errorf("eviction error"),
|
||||||
|
events: []string{"Warning EvictionFailed pod eviction from node node by sigs.k8s.io/descheduler failed: eviction error"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction with dryRun with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
dryRun: true,
|
||||||
|
evictionFailureEventNotification: utilptr.To[bool](true),
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 1,
|
||||||
|
expectedTotalEvictions: 1,
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "one eviction expected without eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 1,
|
||||||
|
expectedTotalEvictions: 1,
|
||||||
|
expectedError: nil,
|
||||||
|
events: []string{"Normal NotSet pod eviction from node node by sigs.k8s.io/descheduler"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction limit exceeded on total without eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](0),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: NewEvictionTotalLimitError(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction limit exceeded on node without eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](0),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: NewEvictionNodeLimitError("node"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction limit exceeded on node without eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](0),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: NewEvictionNamespaceLimitError("default"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction error without eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 0,
|
||||||
|
expectedTotalEvictions: 0,
|
||||||
|
expectedError: fmt.Errorf("eviction error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "eviction without dryRun with eviction failure event notification",
|
||||||
|
pod: pod1,
|
||||||
|
dryRun: true,
|
||||||
|
maxPodsToEvictTotal: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNode: utilptr.To[uint](1),
|
||||||
|
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
|
||||||
|
expectedNodeEvictions: 1,
|
||||||
|
expectedTotalEvictions: 1,
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
fakeClient := fake.NewSimpleClientset(pod1)
|
||||||
|
fakeClient.PrependReactor("create", "pods/eviction", func(action core.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
|
return true, nil, test.expectedError
|
||||||
|
})
|
||||||
|
|
||||||
stubNode := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node"}}
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
// 0 evictions expected
|
eventRecorder := events.NewFakeRecorder(100)
|
||||||
if evictions := podEvictor.NodeEvicted(stubNode); evictions != 0 {
|
|
||||||
t.Errorf("Expected 0 node evictions, got %q instead", evictions)
|
|
||||||
}
|
|
||||||
// 0 evictions expected
|
|
||||||
if evictions := podEvictor.TotalEvicted(); evictions != 0 {
|
|
||||||
t.Errorf("Expected 0 total evictions, got %q instead", evictions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := podEvictor.EvictPod(context.TODO(), pod1, EvictOptions{}); err != nil {
|
podEvictor, err := NewPodEvictor(
|
||||||
t.Errorf("Expected a pod eviction, got an eviction error instead: %v", err)
|
ctx,
|
||||||
}
|
fakeClient,
|
||||||
|
eventRecorder,
|
||||||
|
sharedInformerFactory.Core().V1().Pods().Informer(),
|
||||||
|
initFeatureGates(),
|
||||||
|
NewOptions().
|
||||||
|
WithDryRun(test.dryRun).
|
||||||
|
WithMaxPodsToEvictTotal(test.maxPodsToEvictTotal).
|
||||||
|
WithMaxPodsToEvictPerNode(test.maxPodsToEvictPerNode).
|
||||||
|
WithEvictionFailureEventNotification(test.evictionFailureEventNotification).
|
||||||
|
WithMaxPodsToEvictPerNamespace(test.maxPodsToEvictPerNamespace),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 1 node eviction expected
|
stubNode := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node"}}
|
||||||
if evictions := podEvictor.NodeEvicted(stubNode); evictions != 1 {
|
|
||||||
t.Errorf("Expected 1 node eviction, got %q instead", evictions)
|
|
||||||
}
|
|
||||||
// 1 total eviction expected
|
|
||||||
if evictions := podEvictor.TotalEvicted(); evictions != 1 {
|
|
||||||
t.Errorf("Expected 1 total evictions, got %q instead", evictions)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = podEvictor.EvictPod(context.TODO(), pod1, EvictOptions{})
|
if actualErr := podEvictor.EvictPod(ctx, test.pod, EvictOptions{}); actualErr != nil && actualErr.Error() != test.expectedError.Error() {
|
||||||
if err == nil {
|
t.Errorf("Expected error: %v, got: %v", test.expectedError, actualErr)
|
||||||
t.Errorf("Expected a pod eviction error, got nil instead")
|
}
|
||||||
}
|
|
||||||
switch err.(type) {
|
if evictions := podEvictor.NodeEvicted(stubNode); evictions != test.expectedNodeEvictions {
|
||||||
case *EvictionNodeLimitError:
|
t.Errorf("Expected %d node evictions, got %d instead", test.expectedNodeEvictions, evictions)
|
||||||
// all good
|
}
|
||||||
default:
|
|
||||||
t.Errorf("Expected a pod eviction EvictionNodeLimitError error, got a different error instead: %v", err)
|
if evictions := podEvictor.TotalEvicted(); evictions != test.expectedTotalEvictions {
|
||||||
|
t.Errorf("Expected %d total evictions, got %d instead", test.expectedTotalEvictions, evictions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the events are correct.
|
||||||
|
assertEqualEvents(t, test.events, eventRecorder.Events)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,3 +458,27 @@ func TestEvictionRequestsCacheCleanup(t *testing.T) {
|
|||||||
t.Fatalf("Expected 0 eviction requests, got %v instead", totalERs)
|
t.Fatalf("Expected 0 eviction requests, got %v instead", totalERs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertEqualEvents(t *testing.T, expected []string, actual <-chan string) {
|
||||||
|
t.Logf("Assert for events: %v", expected)
|
||||||
|
c := time.After(wait.ForeverTestTimeout)
|
||||||
|
for _, e := range expected {
|
||||||
|
select {
|
||||||
|
case a := <-actual:
|
||||||
|
if !reflect.DeepEqual(a, e) {
|
||||||
|
t.Errorf("Expected event %q, got %q instead", e, a)
|
||||||
|
}
|
||||||
|
case <-c:
|
||||||
|
t.Errorf("Expected event %q, got nothing", e)
|
||||||
|
// continue iterating to print all expected events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case a := <-actual:
|
||||||
|
t.Errorf("Unexpected event: %q", a)
|
||||||
|
default:
|
||||||
|
return // No more events, as expected.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
policyGroupVersion string
|
policyGroupVersion string
|
||||||
dryRun bool
|
dryRun bool
|
||||||
maxPodsToEvictPerNode *uint
|
maxPodsToEvictPerNode *uint
|
||||||
maxPodsToEvictPerNamespace *uint
|
maxPodsToEvictPerNamespace *uint
|
||||||
maxPodsToEvictTotal *uint
|
maxPodsToEvictTotal *uint
|
||||||
metricsEnabled bool
|
evictionFailureEventNotification bool
|
||||||
|
metricsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOptions returns an Options with default values.
|
// NewOptions returns an Options with default values.
|
||||||
@@ -49,3 +50,10 @@ func (o *Options) WithMetricsEnabled(metricsEnabled bool) *Options {
|
|||||||
o.metricsEnabled = metricsEnabled
|
o.metricsEnabled = metricsEnabled
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Options) WithEvictionFailureEventNotification(evictionFailureEventNotification *bool) *Options {
|
||||||
|
if evictionFailureEventNotification != nil {
|
||||||
|
o.evictionFailureEventNotification = *evictionFailureEventNotification
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user