Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/framework/plugins/defaultevictor/defaultevictor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
153 changes: 148 additions & 5 deletions pkg/framework/plugins/defaultevictor/defaultevictor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
}
})
}
}