1
16
17 package node
18
19 import (
20 "context"
21 "errors"
22 "testing"
23
24 v1 "k8s.io/api/core/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/client-go/kubernetes/fake"
28 k8stesting "k8s.io/client-go/testing"
29 )
30
31
32
33 func TestCheckReadyForTests(t *testing.T) {
34
35 labelNodeRoleControlPlane := "node-role.kubernetes.io/control-plane"
36
37 fromVanillaNode := func(f func(*v1.Node)) v1.Node {
38 vanillaNode := &v1.Node{
39 ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
40 Status: v1.NodeStatus{
41 Conditions: []v1.NodeCondition{
42 {Type: v1.NodeReady, Status: v1.ConditionTrue},
43 },
44 },
45 }
46 f(vanillaNode)
47 return *vanillaNode
48 }
49
50 tcs := []struct {
51 desc string
52 nonblockingTaints string
53 allowedNotReadyNodes int
54 nodes []v1.Node
55 nodeListErr error
56 expected bool
57 expectedErr string
58 }{
59 {
60 desc: "Vanilla node should pass",
61 nodes: []v1.Node{
62 fromVanillaNode(func(n *v1.Node) {}),
63 },
64 expected: true,
65 }, {
66 desc: "Default value for nonblocking taints tolerates control plane taint",
67 nonblockingTaints: `node-role.kubernetes.io/control-plane`,
68 nodes: []v1.Node{
69 fromVanillaNode(func(n *v1.Node) {
70 n.Spec.Taints = []v1.Taint{{Key: labelNodeRoleControlPlane, Effect: v1.TaintEffectNoSchedule}}
71 }),
72 },
73 expected: true,
74 }, {
75 desc: "Tainted node should fail if effect is TaintEffectNoExecute",
76 nonblockingTaints: "bar",
77 nodes: []v1.Node{
78 fromVanillaNode(func(n *v1.Node) {
79 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
80 })},
81 expected: false,
82 }, {
83 desc: "Tainted node can be allowed via allowedNotReadyNodes",
84 nonblockingTaints: "bar",
85 allowedNotReadyNodes: 1,
86 nodes: []v1.Node{
87 fromVanillaNode(func(n *v1.Node) {
88 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
89 })},
90 expected: true,
91 }, {
92 desc: "Multi-node, all OK",
93 nodes: []v1.Node{
94 fromVanillaNode(func(n *v1.Node) {}),
95 fromVanillaNode(func(n *v1.Node) {}),
96 },
97 expected: true,
98 }, {
99 desc: "Multi-node, single blocking node blocks",
100 nodes: []v1.Node{
101 fromVanillaNode(func(n *v1.Node) {}),
102 fromVanillaNode(func(n *v1.Node) {
103 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
104 }),
105 },
106 expected: false,
107 }, {
108 desc: "Multi-node, single blocking node allowed via allowedNotReadyNodes",
109 allowedNotReadyNodes: 1,
110 nodes: []v1.Node{
111 fromVanillaNode(func(n *v1.Node) {}),
112 fromVanillaNode(func(n *v1.Node) {
113 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
114 }),
115 },
116 expected: true,
117 }, {
118 desc: "Multi-node, single blocking node allowed via nonblocking taint",
119 nonblockingTaints: "foo",
120 nodes: []v1.Node{
121 fromVanillaNode(func(n *v1.Node) {}),
122 fromVanillaNode(func(n *v1.Node) {
123 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
124 }),
125 },
126 expected: true,
127 }, {
128 desc: "Multi-node, both blocking nodes allowed via separate nonblocking taints",
129 nonblockingTaints: "foo,bar",
130 nodes: []v1.Node{
131 fromVanillaNode(func(n *v1.Node) {}),
132 fromVanillaNode(func(n *v1.Node) {
133 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
134 }),
135 fromVanillaNode(func(n *v1.Node) {
136 n.Spec.Taints = []v1.Taint{{Key: "bar", Effect: v1.TaintEffectNoSchedule}}
137 }),
138 },
139 expected: true,
140 }, {
141 desc: "Multi-node, one blocking node allowed via nonblocking taints still blocked",
142 nonblockingTaints: "foo,notbar",
143 nodes: []v1.Node{
144 fromVanillaNode(func(n *v1.Node) {}),
145 fromVanillaNode(func(n *v1.Node) {
146 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
147 }),
148 fromVanillaNode(func(n *v1.Node) {
149 n.Spec.Taints = []v1.Taint{{Key: "bar", Effect: v1.TaintEffectNoSchedule}}
150 }),
151 },
152 expected: false,
153 }, {
154 desc: "Errors from node list are reported",
155 nodeListErr: errors.New("Forced error"),
156 expected: false,
157 expectedErr: "Forced error",
158 },
159 }
160
161
162 testLargeClusterThreshold := 1000
163
164 for _, tc := range tcs {
165 t.Run(tc.desc, func(t *testing.T) {
166 c := fake.NewSimpleClientset()
167 c.PrependReactor("list", "nodes", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
168 nodeList := &v1.NodeList{Items: tc.nodes}
169 return true, nodeList, tc.nodeListErr
170 })
171 checkFunc := CheckReadyForTests(context.Background(), c, tc.nonblockingTaints, tc.allowedNotReadyNodes, testLargeClusterThreshold)
172
173
174
175 for attempt := 0; attempt <= 3; attempt++ {
176 out, err := checkFunc(context.Background())
177 expected := tc.expected
178 expectedErr := tc.expectedErr
179 if tc.nodeListErr != nil && attempt < 2 {
180 expected = false
181 expectedErr = ""
182 }
183 if out != expected {
184 t.Errorf("Expected %v but got %v", expected, out)
185 }
186 switch {
187 case err == nil && expectedErr != "":
188 t.Errorf("attempt #%d: expected error %q nil", attempt, expectedErr)
189 case err != nil && err.Error() != expectedErr:
190 t.Errorf("attempt #%d: expected error %q but got %q", attempt, expectedErr, err.Error())
191 }
192 }
193 })
194 }
195 }
196
197 func TestReadyForTests(t *testing.T) {
198 fromVanillaNode := func(f func(*v1.Node)) *v1.Node {
199 vanillaNode := &v1.Node{
200 ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
201 Status: v1.NodeStatus{
202 Conditions: []v1.NodeCondition{
203 {Type: v1.NodeReady, Status: v1.ConditionTrue},
204 },
205 },
206 }
207 f(vanillaNode)
208 return vanillaNode
209 }
210 _ = fromVanillaNode
211 tcs := []struct {
212 desc string
213 node *v1.Node
214 nonblockingTaints string
215 expected bool
216 }{
217 {
218 desc: "Vanilla node should pass",
219 node: fromVanillaNode(func(n *v1.Node) {
220 }),
221 expected: true,
222 }, {
223 desc: "Vanilla node should pass with non-applicable nonblocking taint",
224 nonblockingTaints: "foo",
225 node: fromVanillaNode(func(n *v1.Node) {
226 }),
227 expected: true,
228 }, {
229 desc: "Tainted node should pass if effect is TaintEffectPreferNoSchedule",
230 node: fromVanillaNode(func(n *v1.Node) {
231 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectPreferNoSchedule}}
232 }),
233 expected: true,
234 }, {
235 desc: "Tainted node should fail if effect is TaintEffectNoExecute",
236 node: fromVanillaNode(func(n *v1.Node) {
237 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoExecute}}
238 }),
239 expected: false,
240 }, {
241 desc: "Tainted node should fail",
242 node: fromVanillaNode(func(n *v1.Node) {
243 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
244 }),
245 expected: false,
246 }, {
247 desc: "Tainted node should pass if nonblocking",
248 nonblockingTaints: "foo",
249 node: fromVanillaNode(func(n *v1.Node) {
250 n.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
251 }),
252 expected: true,
253 }, {
254 desc: "Node with network not ready fails",
255 node: fromVanillaNode(func(n *v1.Node) {
256 n.Status.Conditions = append(n.Status.Conditions,
257 v1.NodeCondition{Type: v1.NodeNetworkUnavailable, Status: v1.ConditionTrue},
258 )
259 }),
260 expected: false,
261 }, {
262 desc: "Node fails unless NodeReady status",
263 node: fromVanillaNode(func(n *v1.Node) {
264 n.Status.Conditions = []v1.NodeCondition{}
265 }),
266 expected: false,
267 },
268 }
269
270 for _, tc := range tcs {
271 t.Run(tc.desc, func(t *testing.T) {
272 out := readyForTests(tc.node, tc.nonblockingTaints)
273 if out != tc.expected {
274 t.Errorf("Expected %v but got %v", tc.expected, out)
275 }
276 })
277 }
278 }
279
View as plain text