k8s.io/kubernetes@v1.29.3/test/e2e/framework/node/wait_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  // TestCheckReadyForTests specifically is concerned about the multi-node logic
    32  // since single node checks are in TestReadyForTests.
    33  func TestCheckReadyForTests(t *testing.T) {
    34  	// This is a duplicate definition of the constant in pkg/controller/service/controller.go
    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  	// Only determines some logging functionality; not relevant so set to a large value.
   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  			// The check function returns "false, nil" during its
   173  			// first two calls, therefore we have to try several
   174  			// times until we get the expected error.
   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  }