mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-05-22 09:03:06 +00:00
chore(defaultevictor): add matchlabels compatibility to the namespaceselector (#1853)
This commit is contained in:
@@ -90,7 +90,7 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ev.args.NamespaceLabelSelector != nil && len(ev.args.NamespaceLabelSelector.MatchLabels) > 0 {
|
||||
if ev.args.NamespaceLabelSelector != nil && (len(ev.args.NamespaceLabelSelector.MatchLabels) > 0 || len(ev.args.NamespaceLabelSelector.MatchExpressions) > 0) {
|
||||
selector, nslErr := metav1.LabelSelectorAsSelector(ev.args.NamespaceLabelSelector)
|
||||
if nslErr != nil {
|
||||
return nil, fmt.Errorf("unable to convert namespaceLabelSelector to label selector: %w", nslErr)
|
||||
@@ -447,7 +447,7 @@ func (d *DefaultEvictor) PreEvictionFilter(pod *v1.Pod) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if d.args.NamespaceLabelSelector == nil || len(d.args.NamespaceLabelSelector.MatchLabels) == 0 {
|
||||
if d.args.NamespaceLabelSelector == nil || (len(d.args.NamespaceLabelSelector.MatchLabels) == 0 && len(d.args.NamespaceLabelSelector.MatchExpressions) == 0) {
|
||||
return true
|
||||
}
|
||||
indexName := namespaceWithLabelSelector + d.handle.PluginInstanceID()
|
||||
|
||||
@@ -1030,13 +1030,13 @@ func initializePlugin(ctx context.Context, test testCase) (frameworktypes.Plugin
|
||||
objs = append(objs, ns)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewSimpleClientset(objs...)
|
||||
fakeClient := fake.NewClientset(objs...)
|
||||
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
podInformer := sharedInformerFactory.Core().V1().Pods().Informer()
|
||||
_ = sharedInformerFactory.Policy().V1().PodDisruptionBudgets().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().PersistentVolumeClaims().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Informer()
|
||||
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build get pods assigned to node function error: %v", err)
|
||||
@@ -1283,9 +1283,7 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
|
||||
fakeClient := fake.NewClientset(node, nsProd, nsBackend, nsTest, podInProd, podInBackend, podInTest)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Lister()
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Informer()
|
||||
|
||||
// Create Profile 1: targets namespaces with env=prod
|
||||
profile1Args := &DefaultEvictorArgs{
|
||||
@@ -1328,6 +1326,9 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
|
||||
t.Fatalf("unable to initialize profile2 plugin: %v", err)
|
||||
}
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
// Test Profile 1: evicts pods in ns-prod, reject others
|
||||
t.Run("profile1", func(t *testing.T) {
|
||||
profile1 := profile1Plugin.(*DefaultEvictor)
|
||||
@@ -1362,3 +1363,145 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNamespaceLabelSelectorAllOperations(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
node := test.BuildTestNode("node1", 1000, 2000, 10, nil)
|
||||
|
||||
nsProd := test.BuildTestNamespace("ns-prod")
|
||||
nsProd.Labels = map[string]string{"env": "prod", "tier": "frontend"}
|
||||
|
||||
nsStaging := test.BuildTestNamespace("ns-staging")
|
||||
nsStaging.Labels = map[string]string{"env": "staging", "tier": "backend"}
|
||||
|
||||
nsTest := test.BuildTestNamespace("ns-test")
|
||||
nsTest.Labels = map[string]string{"env": "test"}
|
||||
|
||||
nsNoLabels := test.BuildTestNamespace("ns-nolabels")
|
||||
nsNoLabels.Labels = map[string]string{}
|
||||
|
||||
podInProdFrontend := test.BuildTestPod("pod-in-prod", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsProd.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
podInStagingBackend := test.BuildTestPod("pod-in-staging", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsStaging.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
podInTest := test.BuildTestPod("pod-in-test", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsTest.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
podInNoLabels := test.BuildTestPod("pod-in-nolabels", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsNoLabels.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
fakeClient := fake.NewClientset(node, nsProd, nsStaging, nsTest, nsNoLabels, podInProdFrontend, podInStagingBackend, podInTest, podInNoLabels)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
getPodAssignedToNode, _ := podutil.BuildGetPodsAssignedToNodeFunc(sharedInformerFactory.Core().V1().Pods().Informer())
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args *DefaultEvictorArgs
|
||||
pass []*v1.Pod
|
||||
fail []*v1.Pod
|
||||
}{
|
||||
{
|
||||
name: "MatchLabels and MatchExpressions In",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"tier": "frontend"},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpIn, Values: []string{"prod", "staging"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInProdFrontend},
|
||||
fail: []*v1.Pod{podInStagingBackend, podInTest},
|
||||
},
|
||||
{
|
||||
name: "MatchLabels and MatchExpressions Exists",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"tier": "backend"},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpExists},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInStagingBackend},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInTest},
|
||||
},
|
||||
{
|
||||
name: "Exists and In",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "tier", Operator: metav1.LabelSelectorOpExists},
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpIn, Values: []string{"staging"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInStagingBackend},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInTest},
|
||||
},
|
||||
{
|
||||
name: "DoesNotExist",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "tier", Operator: metav1.LabelSelectorOpDoesNotExist},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInTest, podInNoLabels},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInStagingBackend},
|
||||
},
|
||||
{
|
||||
name: "NotIn",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"prod", "test"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInStagingBackend},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInTest},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
plugin, err := New(ctx, tc.args, &frameworkfake.HandleImpl{
|
||||
ClientsetImpl: fakeClient,
|
||||
GetPodsAssignedToNodeFuncImpl: getPodAssignedToNode,
|
||||
SharedInformerFactoryImpl: sharedInformerFactory,
|
||||
PluginInstanceIDImpl: "test-" + tc.name,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initialize plugin: %v", err)
|
||||
}
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
evictor := plugin.(*DefaultEvictor)
|
||||
for _, pod := range tc.pass {
|
||||
if result := evictor.PreEvictionFilter(pod); !result {
|
||||
t.Errorf("pod %s expected to pass (true), got false", pod.Name)
|
||||
}
|
||||
}
|
||||
for _, pod := range tc.fail {
|
||||
if result := evictor.PreEvictionFilter(pod); result {
|
||||
t.Errorf("pod %s expected to fail (false), got true", pod.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user