Skip to content

Commit 85004f0

Browse files
committed
kubectl/drain add support for custom pod filters
Currently, there is no way to supply custom pod filters to exclude pods meeting arbitrary criteria when using drain as a library. This commit adds the ability for developers to add custom filters to the drain operation when utilizing drain as a library. This commit exports certain types that were previously private to allow for better code reuse. This commit also adds appropriate unit tests.
1 parent 5ab1f34 commit 85004f0

File tree

4 files changed

+178
-88
lines changed

4 files changed

+178
-88
lines changed

staging/src/k8s.io/kubectl/pkg/drain/drain.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ type Helper struct {
6565
// won't drain otherwise
6666
SkipWaitForDeleteTimeoutSeconds int
6767

68+
// AdditionalFilters are applied sequentially after base drain filters to
69+
// exclude pods using custom logic. Any filter that returns PodDeleteStatus
70+
// with Delete == false will immediately stop execution of further filters.
71+
AdditionalFilters []PodFilter
72+
6873
Out io.Writer
6974
ErrOut io.Writer
7075

@@ -169,7 +174,7 @@ func (d *Helper) EvictPod(pod corev1.Pod, policyGroupVersion string) error {
169174
// or error if it cannot list pods. All pods that are ready to be deleted can be obtained with .Pods(),
170175
// and string with all warning can be obtained with .Warnings(), and .Errors() for all errors that
171176
// occurred during deletion.
172-
func (d *Helper) GetPodsForDeletion(nodeName string) (*podDeleteList, []error) {
177+
func (d *Helper) GetPodsForDeletion(nodeName string) (*PodDeleteList, []error) {
173178
labelSelector, err := labels.Parse(d.PodSelector)
174179
if err != nil {
175180
return nil, []error{err}
@@ -182,35 +187,37 @@ func (d *Helper) GetPodsForDeletion(nodeName string) (*podDeleteList, []error) {
182187
return nil, []error{err}
183188
}
184189

185-
pods := []podDelete{}
190+
list := filterPods(podList, d.makeFilters())
191+
if errs := list.errors(); len(errs) > 0 {
192+
return list, errs
193+
}
194+
195+
return list, nil
196+
}
186197

198+
func filterPods(podList *corev1.PodList, filters []PodFilter) *PodDeleteList {
199+
pods := []PodDelete{}
187200
for _, pod := range podList.Items {
188-
var status podDeleteStatus
189-
for _, filter := range d.makeFilters() {
201+
var status PodDeleteStatus
202+
for _, filter := range filters {
190203
status = filter(pod)
191-
if !status.delete {
204+
if !status.Delete {
192205
// short-circuit as soon as pod is filtered out
193206
// at that point, there is no reason to run pod
194207
// through any additional filters
195208
break
196209
}
197210
}
198-
// Add the pod to podDeleteList no matter what podDeleteStatus is,
199-
// those pods whose podDeleteStatus is false like DaemonSet will
211+
// Add the pod to PodDeleteList no matter what PodDeleteStatus is,
212+
// those pods whose PodDeleteStatus is false like DaemonSet will
200213
// be catched by list.errors()
201-
pods = append(pods, podDelete{
202-
pod: pod,
203-
status: status,
214+
pods = append(pods, PodDelete{
215+
Pod: pod,
216+
Status: status,
204217
})
205218
}
206-
207-
list := &podDeleteList{items: pods}
208-
209-
if errs := list.errors(); len(errs) > 0 {
210-
return list, errs
211-
}
212-
213-
return list, nil
219+
list := &PodDeleteList{items: pods}
220+
return list
214221
}
215222

216223
// DeleteOrEvictPods deletes or evicts the pods on the api server

staging/src/k8s.io/kubectl/pkg/drain/drain_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,72 @@ func TestDeleteOrEvict(t *testing.T) {
389389
})
390390
}
391391
}
392+
393+
func mockFilterSkip(_ corev1.Pod) PodDeleteStatus {
394+
return MakePodDeleteStatusSkip()
395+
}
396+
397+
func mockFilterOkay(_ corev1.Pod) PodDeleteStatus {
398+
return MakePodDeleteStatusOkay()
399+
}
400+
401+
func TestFilterPods(t *testing.T) {
402+
tCases := []struct {
403+
description string
404+
expectedPodListLen int
405+
additionalFilters []PodFilter
406+
}{
407+
{
408+
description: "AdditionalFilter skip all",
409+
expectedPodListLen: 0,
410+
additionalFilters: []PodFilter{
411+
mockFilterSkip,
412+
mockFilterOkay,
413+
},
414+
},
415+
{
416+
description: "AdditionalFilter okay all",
417+
expectedPodListLen: 1,
418+
additionalFilters: []PodFilter{
419+
mockFilterOkay,
420+
},
421+
},
422+
{
423+
description: "AdditionalFilter Skip after Okay all skip",
424+
expectedPodListLen: 0,
425+
additionalFilters: []PodFilter{
426+
mockFilterOkay,
427+
mockFilterSkip,
428+
},
429+
},
430+
{
431+
description: "No additionalFilters okay all",
432+
expectedPodListLen: 1,
433+
},
434+
}
435+
for _, tc := range tCases {
436+
t.Run(tc.description, func(t *testing.T) {
437+
h := &Helper{
438+
Force: true,
439+
AdditionalFilters: tc.additionalFilters,
440+
}
441+
pod := corev1.Pod{
442+
ObjectMeta: metav1.ObjectMeta{
443+
Name: "pod",
444+
Namespace: "default",
445+
},
446+
}
447+
podList := corev1.PodList{
448+
Items: []corev1.Pod{
449+
pod,
450+
},
451+
}
452+
453+
list := filterPods(&podList, h.makeFilters())
454+
podsLen := len(list.Pods())
455+
if podsLen != tc.expectedPodListLen {
456+
t.Errorf("%s: unexpected evictions; actual %v; expected %v", tc.description, podsLen, tc.expectedPodListLen)
457+
}
458+
})
459+
}
460+
}

staging/src/k8s.io/kubectl/pkg/drain/filter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ func TestSkipDeletedFilter(t *testing.T) {
6363
}
6464

6565
podDeleteStatus := h.skipDeletedFilter(pod)
66-
if podDeleteStatus.delete != tc.expectedDelete {
67-
t.Errorf("test %v: unexpected podDeleteStatus.delete; actual %v; expected %v", i, podDeleteStatus.delete, tc.expectedDelete)
66+
if podDeleteStatus.Delete != tc.expectedDelete {
67+
t.Errorf("test %v: unexpected podDeleteStatus.delete; actual %v; expected %v", i, podDeleteStatus.Delete, tc.expectedDelete)
6868
}
6969
}
7070
}

0 commit comments

Comments
 (0)