Skip to content

Commit 4bba444

Browse files
committed
Add tests for newly exposed drain code
1 parent c74f2f6 commit 4bba444

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ go_test(
5151
embed = [":go_default_library"],
5252
deps = [
5353
"//staging/src/k8s.io/api/core/v1:go_default_library",
54+
"//staging/src/k8s.io/api/policy/v1beta1:go_default_library",
5455
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
5556
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
57+
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
5658
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
5759
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
5860
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
61+
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
62+
"//staging/src/k8s.io/client-go/testing:go_default_library",
5963
],
6064
)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
// RunNodeDrain shows the canonical way to drain a node.
3232
// You should first cordon the node, e.g. using RunCordonOrUncordon
3333
func RunNodeDrain(drainer *Helper, nodeName string) error {
34+
// TODO(justinsb): Ensure we have adequate e2e coverage of this function in library consumers
3435
list, errs := drainer.GetPodsForDeletion(nodeName)
3536
if errs != nil {
3637
return utilerrors.NewAggregate(errs)
@@ -48,6 +49,7 @@ func RunNodeDrain(drainer *Helper, nodeName string) error {
4849

4950
// RunCordonOrUncordon demonstrates the canonical way to cordon or uncordon a Node
5051
func RunCordonOrUncordon(drainer *Helper, node *corev1.Node, desired bool) error {
52+
// TODO(justinsb): Ensure we have adequate e2e coverage of this function in library consumers
5153
c := NewCordonHelper(node)
5254

5355
if updateRequired := c.UpdateIfRequired(desired); !updateRequired {

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

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,23 @@ package drain
1919
import (
2020
"errors"
2121
"fmt"
22+
"os"
23+
"reflect"
24+
"sort"
2225
"strconv"
2326
"testing"
2427
"time"
2528

2629
corev1 "k8s.io/api/core/v1"
30+
policyv1beta1 "k8s.io/api/policy/v1beta1"
2731
apierrors "k8s.io/apimachinery/pkg/api/errors"
2832
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33+
"k8s.io/apimachinery/pkg/runtime"
2934
"k8s.io/apimachinery/pkg/runtime/schema"
3035
"k8s.io/apimachinery/pkg/types"
3136
"k8s.io/apimachinery/pkg/util/wait"
37+
"k8s.io/client-go/kubernetes/fake"
38+
ktest "k8s.io/client-go/testing"
3239
)
3340

3441
func TestDeletePods(t *testing.T) {
@@ -145,3 +152,155 @@ func createPods(ifCreateNewPods bool) (map[string]corev1.Pod, []corev1.Pod) {
145152
}
146153
return podMap, podSlice
147154
}
155+
156+
// addEvictionSupport implements simple fake eviction support on the fake.Clientset
157+
func addEvictionSupport(t *testing.T, k *fake.Clientset) {
158+
podsEviction := metav1.APIResource{
159+
Name: "pods/eviction",
160+
Kind: "Eviction",
161+
Group: "",
162+
Version: "v1",
163+
}
164+
coreResources := &metav1.APIResourceList{
165+
GroupVersion: "v1",
166+
APIResources: []metav1.APIResource{podsEviction},
167+
}
168+
169+
policyResources := &metav1.APIResourceList{
170+
GroupVersion: "policy/v1",
171+
}
172+
k.Resources = append(k.Resources, coreResources, policyResources)
173+
174+
// Delete pods when evict is called
175+
k.PrependReactor("create", "pods", func(action ktest.Action) (bool, runtime.Object, error) {
176+
if action.GetSubresource() != "eviction" {
177+
return false, nil, nil
178+
}
179+
180+
eviction := *action.(ktest.CreateAction).GetObject().(*policyv1beta1.Eviction)
181+
// Avoid the lock
182+
go func() {
183+
err := k.CoreV1().Pods(eviction.Namespace).Delete(eviction.Name, &metav1.DeleteOptions{})
184+
if err != nil {
185+
// Errorf because we can't call Fatalf from another goroutine
186+
t.Errorf("failed to delete pod: %s/%s", eviction.Namespace, eviction.Name)
187+
}
188+
}()
189+
190+
return true, nil, nil
191+
})
192+
}
193+
194+
func TestCheckEvictionSupport(t *testing.T) {
195+
for _, evictionSupported := range []bool{true, false} {
196+
evictionSupported := evictionSupported
197+
t.Run(fmt.Sprintf("evictionSupported=%v", evictionSupported),
198+
func(t *testing.T) {
199+
k := fake.NewSimpleClientset()
200+
if evictionSupported {
201+
addEvictionSupport(t, k)
202+
}
203+
204+
apiGroup, err := CheckEvictionSupport(k)
205+
if err != nil {
206+
t.Fatalf("unexpected error: %v", err)
207+
}
208+
expectedAPIGroup := ""
209+
if evictionSupported {
210+
expectedAPIGroup = "policy/v1"
211+
}
212+
if apiGroup != expectedAPIGroup {
213+
t.Fatalf("expected apigroup %q, actual=%q", expectedAPIGroup, apiGroup)
214+
}
215+
})
216+
}
217+
}
218+
219+
func TestDeleteOrEvict(t *testing.T) {
220+
for _, evictionSupported := range []bool{true, false} {
221+
evictionSupported := evictionSupported
222+
t.Run(fmt.Sprintf("evictionSupported=%v", evictionSupported),
223+
func(t *testing.T) {
224+
h := &Helper{
225+
Out: os.Stdout,
226+
GracePeriodSeconds: 10,
227+
}
228+
229+
// Create 4 pods, and try to remove the first 2
230+
var expectedEvictions []policyv1beta1.Eviction
231+
var create []runtime.Object
232+
deletePods := []corev1.Pod{}
233+
for i := 1; i <= 4; i++ {
234+
pod := &corev1.Pod{}
235+
pod.Name = fmt.Sprintf("mypod-%d", i)
236+
pod.Namespace = "default"
237+
238+
create = append(create, pod)
239+
if i <= 2 {
240+
deletePods = append(deletePods, *pod)
241+
242+
if evictionSupported {
243+
eviction := policyv1beta1.Eviction{}
244+
eviction.Kind = "Eviction"
245+
eviction.APIVersion = "policy/v1"
246+
eviction.Namespace = pod.Namespace
247+
eviction.Name = pod.Name
248+
249+
gracePeriodSeconds := int64(h.GracePeriodSeconds)
250+
eviction.DeleteOptions = &metav1.DeleteOptions{
251+
GracePeriodSeconds: &gracePeriodSeconds,
252+
}
253+
254+
expectedEvictions = append(expectedEvictions, eviction)
255+
}
256+
}
257+
}
258+
259+
// Build the fake client
260+
k := fake.NewSimpleClientset(create...)
261+
if evictionSupported {
262+
addEvictionSupport(t, k)
263+
}
264+
h.Client = k
265+
266+
// Do the eviction
267+
if err := h.DeleteOrEvictPods(deletePods); err != nil {
268+
t.Fatalf("error from DeleteOrEvictPods: %v", err)
269+
}
270+
271+
// Test that other pods are still there
272+
var remainingPods []string
273+
{
274+
podList, err := k.CoreV1().Pods("").List(metav1.ListOptions{})
275+
if err != nil {
276+
t.Fatalf("error listing pods: %v", err)
277+
}
278+
279+
for _, pod := range podList.Items {
280+
remainingPods = append(remainingPods, pod.Namespace+"/"+pod.Name)
281+
}
282+
sort.Strings(remainingPods)
283+
}
284+
expected := []string{"default/mypod-3", "default/mypod-4"}
285+
if !reflect.DeepEqual(remainingPods, expected) {
286+
t.Errorf("unexpected remaining pods after DeleteOrEvictPods; actual %v; expected %v", remainingPods, expected)
287+
}
288+
289+
// Test that pods were evicted as expected
290+
var actualEvictions []policyv1beta1.Eviction
291+
for _, action := range k.Actions() {
292+
if action.GetVerb() != "create" || action.GetResource().Resource != "pods" || action.GetSubresource() != "eviction" {
293+
continue
294+
}
295+
eviction := *action.(ktest.CreateAction).GetObject().(*policyv1beta1.Eviction)
296+
actualEvictions = append(actualEvictions, eviction)
297+
}
298+
sort.Slice(actualEvictions, func(i, j int) bool {
299+
return actualEvictions[i].Name < actualEvictions[j].Name
300+
})
301+
if !reflect.DeepEqual(actualEvictions, expectedEvictions) {
302+
t.Errorf("unexpected evictions; actual %v; expected %v", actualEvictions, expectedEvictions)
303+
}
304+
})
305+
}
306+
}

0 commit comments

Comments
 (0)