Skip to content

Commit 0f46a8a

Browse files
authored
Merge pull request kubernetes#81043 from johnSchnake/whitelistedTaints
Add new flag for whitelisting node taints
2 parents afe8543 + 3c53481 commit 0f46a8a

File tree

7 files changed

+446
-112
lines changed

7 files changed

+446
-112
lines changed

test/e2e/framework/BUILD

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
package(default_visibility = ["//visibility:public"])
2-
31
load("@io_bazel_rules_go//go:def.bzl", "go_library")
42

53
go_library(
@@ -28,6 +26,7 @@ go_library(
2826
"util.go",
2927
],
3028
importpath = "k8s.io/kubernetes/test/e2e/framework",
29+
visibility = ["//visibility:public"],
3130
deps = [
3231
"//pkg/api/v1/pod:go_default_library",
3332
"//pkg/apis/core:go_default_library",
@@ -39,8 +38,6 @@ go_library(
3938
"//pkg/kubelet/events:go_default_library",
4039
"//pkg/kubelet/sysctl:go_default_library",
4140
"//pkg/master/ports:go_default_library",
42-
"//pkg/scheduler/algorithm/predicates:go_default_library",
43-
"//pkg/scheduler/nodeinfo:go_default_library",
4441
"//pkg/util/taints:go_default_library",
4542
"//pkg/version:go_default_library",
4643
"//pkg/volume/util:go_default_library",
@@ -155,4 +152,5 @@ filegroup(
155152
"//test/e2e/framework/volume:all-srcs",
156153
],
157154
tags = ["automanaged"],
155+
visibility = ["//visibility:public"],
158156
)

test/e2e/framework/node/BUILD

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@io_bazel_rules_go//go:def.bzl", "go_library")
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "go_default_library",
@@ -37,3 +37,17 @@ filegroup(
3737
tags = ["automanaged"],
3838
visibility = ["//visibility:public"],
3939
)
40+
41+
go_test(
42+
name = "go_default_test",
43+
srcs = ["wait_test.go"],
44+
embed = [":go_default_library"],
45+
deps = [
46+
"//staging/src/k8s.io/api/core/v1:go_default_library",
47+
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
48+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
49+
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
50+
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
51+
"//staging/src/k8s.io/client-go/testing:go_default_library",
52+
],
53+
)

test/e2e/framework/node/resource.go

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package node
1919
import (
2020
"fmt"
2121
"net"
22+
"strings"
2223
"time"
2324

2425
v1 "k8s.io/api/core/v1"
@@ -342,7 +343,7 @@ func GetReadySchedulableNodesOrDie(c clientset.Interface) (nodes *v1.NodeList, e
342343
// previous tests may have cause failures of some nodes. Let's skip
343344
// 'Not Ready' nodes, just in case (there is no need to fail the test).
344345
Filter(nodes, func(node v1.Node) bool {
345-
return isNodeSchedulable(&node) && isNodeUntainted(&node)
346+
return IsNodeSchedulable(&node) && IsNodeUntainted(&node)
346347
})
347348
return nodes, nil
348349
}
@@ -357,7 +358,7 @@ func GetReadyNodesIncludingTainted(c clientset.Interface) (nodes *v1.NodeList, e
357358
return nil, fmt.Errorf("listing schedulable nodes error: %s", err)
358359
}
359360
Filter(nodes, func(node v1.Node) bool {
360-
return isNodeSchedulable(&node)
361+
return IsNodeSchedulable(&node)
361362
})
362363
return nodes, nil
363364
}
@@ -373,16 +374,22 @@ func GetMasterAndWorkerNodes(c clientset.Interface) (sets.String, *v1.NodeList,
373374
for _, n := range all.Items {
374375
if system.DeprecatedMightBeMasterNode(n.Name) {
375376
masters.Insert(n.Name)
376-
} else if isNodeSchedulable(&n) && isNodeUntainted(&n) {
377+
} else if IsNodeSchedulable(&n) && IsNodeUntainted(&n) {
377378
nodes.Items = append(nodes.Items, n)
378379
}
379380
}
380381
return masters, nodes, nil
381382
}
382383

383-
// Test whether a fake pod can be scheduled on "node", given its current taints.
384+
// IsNodeUntainted tests whether a fake pod can be scheduled on "node", given its current taints.
384385
// TODO: need to discuss wether to return bool and error type
385-
func isNodeUntainted(node *v1.Node) bool {
386+
func IsNodeUntainted(node *v1.Node) bool {
387+
return isNodeUntaintedWithNonblocking(node, "")
388+
}
389+
390+
// isNodeUntaintedWithNonblocking tests whether a fake pod can be scheduled on "node"
391+
// but allows for taints in the list of non-blocking taints.
392+
func isNodeUntaintedWithNonblocking(node *v1.Node, nonblockingTaints string) bool {
386393
fakePod := &v1.Pod{
387394
TypeMeta: metav1.TypeMeta{
388395
Kind: "Pod",
@@ -401,8 +408,30 @@ func isNodeUntainted(node *v1.Node) bool {
401408
},
402409
},
403410
}
411+
404412
nodeInfo := schedulernodeinfo.NewNodeInfo()
405-
nodeInfo.SetNode(node)
413+
414+
// Simple lookup for nonblocking taints based on comma-delimited list.
415+
nonblockingTaintsMap := map[string]struct{}{}
416+
for _, t := range strings.Split(nonblockingTaints, ",") {
417+
if strings.TrimSpace(t) != "" {
418+
nonblockingTaintsMap[strings.TrimSpace(t)] = struct{}{}
419+
}
420+
}
421+
422+
if len(nonblockingTaintsMap) > 0 {
423+
nodeCopy := node.DeepCopy()
424+
nodeCopy.Spec.Taints = []v1.Taint{}
425+
for _, v := range node.Spec.Taints {
426+
if _, isNonblockingTaint := nonblockingTaintsMap[v.Key]; !isNonblockingTaint {
427+
nodeCopy.Spec.Taints = append(nodeCopy.Spec.Taints, v)
428+
}
429+
}
430+
nodeInfo.SetNode(nodeCopy)
431+
} else {
432+
nodeInfo.SetNode(node)
433+
}
434+
406435
fit, _, err := predicates.PodToleratesNodeTaints(fakePod, nil, nodeInfo)
407436
if err != nil {
408437
e2elog.Failf("Can't test predicates for node %s: %v", node.Name, err)
@@ -411,15 +440,48 @@ func isNodeUntainted(node *v1.Node) bool {
411440
return fit
412441
}
413442

414-
// Node is schedulable if:
443+
// IsNodeSchedulable returns true if:
415444
// 1) doesn't have "unschedulable" field set
416-
// 2) it's Ready condition is set to true
417-
// 3) doesn't have NetworkUnavailable condition set to true
418-
func isNodeSchedulable(node *v1.Node) bool {
445+
// 2) it also returns true from IsNodeReady
446+
func IsNodeSchedulable(node *v1.Node) bool {
447+
if node == nil {
448+
return false
449+
}
450+
return !node.Spec.Unschedulable && IsNodeReady(node)
451+
}
452+
453+
// IsNodeReady returns true if:
454+
// 1) it's Ready condition is set to true
455+
// 2) doesn't have NetworkUnavailable condition set to true
456+
func IsNodeReady(node *v1.Node) bool {
419457
nodeReady := IsConditionSetAsExpected(node, v1.NodeReady, true)
420458
networkReady := IsConditionUnset(node, v1.NodeNetworkUnavailable) ||
421459
IsConditionSetAsExpectedSilent(node, v1.NodeNetworkUnavailable, false)
422-
return !node.Spec.Unschedulable && nodeReady && networkReady
460+
return nodeReady && networkReady
461+
}
462+
463+
// hasNonblockingTaint returns true if the node contains at least
464+
// one taint with a key matching the regexp.
465+
func hasNonblockingTaint(node *v1.Node, nonblockingTaints string) bool {
466+
if node == nil {
467+
return false
468+
}
469+
470+
// Simple lookup for nonblocking taints based on comma-delimited list.
471+
nonblockingTaintsMap := map[string]struct{}{}
472+
for _, t := range strings.Split(nonblockingTaints, ",") {
473+
if strings.TrimSpace(t) != "" {
474+
nonblockingTaintsMap[strings.TrimSpace(t)] = struct{}{}
475+
}
476+
}
477+
478+
for _, taint := range node.Spec.Taints {
479+
if _, hasNonblockingTaint := nonblockingTaintsMap[taint.Key]; hasNonblockingTaint {
480+
return true
481+
}
482+
}
483+
484+
return false
423485
}
424486

425487
// PodNodePairs return podNode pairs for all pods in a namespace

test/e2e/framework/node/wait.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,76 @@ func checkWaitListSchedulableNodes(c clientset.Interface) (*v1.NodeList, error)
206206
}
207207
return nodes, nil
208208
}
209+
210+
// CheckReadyForTests returns a method usable in polling methods which will check that the nodes are
211+
// in a testable state based on schedulability.
212+
func CheckReadyForTests(c clientset.Interface, nonblockingTaints string, allowedNotReadyNodes, largeClusterThreshold int) func() (bool, error) {
213+
attempt := 0
214+
var notSchedulable []*v1.Node
215+
return func() (bool, error) {
216+
attempt++
217+
notSchedulable = nil
218+
opts := metav1.ListOptions{
219+
ResourceVersion: "0",
220+
FieldSelector: fields.Set{"spec.unschedulable": "false"}.AsSelector().String(),
221+
}
222+
nodes, err := c.CoreV1().Nodes().List(opts)
223+
if err != nil {
224+
e2elog.Logf("Unexpected error listing nodes: %v", err)
225+
if testutils.IsRetryableAPIError(err) {
226+
return false, nil
227+
}
228+
return false, err
229+
}
230+
for i := range nodes.Items {
231+
node := &nodes.Items[i]
232+
if !readyForTests(node, nonblockingTaints) {
233+
notSchedulable = append(notSchedulable, node)
234+
}
235+
}
236+
// Framework allows for <TestContext.AllowedNotReadyNodes> nodes to be non-ready,
237+
// to make it possible e.g. for incorrect deployment of some small percentage
238+
// of nodes (which we allow in cluster validation). Some nodes that are not
239+
// provisioned correctly at startup will never become ready (e.g. when something
240+
// won't install correctly), so we can't expect them to be ready at any point.
241+
//
242+
// However, we only allow non-ready nodes with some specific reasons.
243+
if len(notSchedulable) > 0 {
244+
// In large clusters, log them only every 10th pass.
245+
if len(nodes.Items) < largeClusterThreshold || attempt%10 == 0 {
246+
e2elog.Logf("Unschedulable nodes:")
247+
for i := range notSchedulable {
248+
e2elog.Logf("-> %s Ready=%t Network=%t Taints=%v NonblockingTaints:%v",
249+
notSchedulable[i].Name,
250+
IsConditionSetAsExpectedSilent(notSchedulable[i], v1.NodeReady, true),
251+
IsConditionSetAsExpectedSilent(notSchedulable[i], v1.NodeNetworkUnavailable, false),
252+
notSchedulable[i].Spec.Taints,
253+
nonblockingTaints,
254+
)
255+
256+
}
257+
e2elog.Logf("================================")
258+
}
259+
}
260+
return len(notSchedulable) <= allowedNotReadyNodes, nil
261+
}
262+
}
263+
264+
// readyForTests determines whether or not we should continue waiting for the nodes
265+
// to enter a testable state. By default this means it is schedulable, NodeReady, and untainted.
266+
// Nodes with taints nonblocking taints are permitted to have that taint and
267+
// also have their node.Spec.Unschedulable field ignored for the purposes of this function.
268+
func readyForTests(node *v1.Node, nonblockingTaints string) bool {
269+
if hasNonblockingTaint(node, nonblockingTaints) {
270+
// If the node has one of the nonblockingTaints taints; just check that it is ready
271+
// and don't require node.Spec.Unschedulable to be set either way.
272+
if !IsNodeReady(node) || !isNodeUntaintedWithNonblocking(node, nonblockingTaints) {
273+
return false
274+
}
275+
} else {
276+
if !IsNodeSchedulable(node) || !IsNodeUntainted(node) {
277+
return false
278+
}
279+
}
280+
return true
281+
}

0 commit comments

Comments
 (0)