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

     1  /*
     2  Copyright 2014 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  	"fmt"
    22  	"time"
    23  
    24  	"github.com/onsi/ginkgo/v2"
    25  	"github.com/onsi/gomega"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	testutils "k8s.io/kubernetes/test/utils"
    32  
    33  	"k8s.io/kubernetes/test/e2e/framework"
    34  )
    35  
    36  const (
    37  	// Minimal number of nodes for the cluster to be considered large.
    38  	largeClusterThreshold = 100
    39  )
    40  
    41  // WaitForAllNodesSchedulable waits up to timeout for all
    42  // (but TestContext.AllowedNotReadyNodes) to become schedulable.
    43  func WaitForAllNodesSchedulable(ctx context.Context, c clientset.Interface, timeout time.Duration) error {
    44  	if framework.TestContext.AllowedNotReadyNodes == -1 {
    45  		return nil
    46  	}
    47  
    48  	framework.Logf("Waiting up to %v for all (but %d) nodes to be schedulable", timeout, framework.TestContext.AllowedNotReadyNodes)
    49  	return wait.PollImmediateWithContext(
    50  		ctx,
    51  		30*time.Second,
    52  		timeout,
    53  		CheckReadyForTests(ctx, c, framework.TestContext.NonblockingTaints, framework.TestContext.AllowedNotReadyNodes, largeClusterThreshold),
    54  	)
    55  }
    56  
    57  // AddOrUpdateLabelOnNode adds the given label key and value to the given node or updates value.
    58  func AddOrUpdateLabelOnNode(c clientset.Interface, nodeName string, labelKey, labelValue string) {
    59  	framework.ExpectNoError(testutils.AddLabelsToNode(c, nodeName, map[string]string{labelKey: labelValue}))
    60  }
    61  
    62  // ExpectNodeHasLabel expects that the given node has the given label pair.
    63  func ExpectNodeHasLabel(ctx context.Context, c clientset.Interface, nodeName string, labelKey string, labelValue string) {
    64  	ginkgo.By("verifying the node has the label " + labelKey + " " + labelValue)
    65  	node, err := c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
    66  	framework.ExpectNoError(err)
    67  	gomega.Expect(node.Labels).To(gomega.HaveKeyWithValue(labelKey, labelValue))
    68  }
    69  
    70  // RemoveLabelOffNode is for cleaning up labels temporarily added to node,
    71  // won't fail if target label doesn't exist or has been removed.
    72  func RemoveLabelOffNode(c clientset.Interface, nodeName string, labelKey string) {
    73  	ginkgo.By("removing the label " + labelKey + " off the node " + nodeName)
    74  	framework.ExpectNoError(testutils.RemoveLabelOffNode(c, nodeName, []string{labelKey}))
    75  
    76  	ginkgo.By("verifying the node doesn't have the label " + labelKey)
    77  	framework.ExpectNoError(testutils.VerifyLabelsRemoved(c, nodeName, []string{labelKey}))
    78  }
    79  
    80  // ExpectNodeHasTaint expects that the node has the given taint.
    81  func ExpectNodeHasTaint(ctx context.Context, c clientset.Interface, nodeName string, taint *v1.Taint) {
    82  	ginkgo.By("verifying the node has the taint " + taint.ToString())
    83  	if has, err := NodeHasTaint(ctx, c, nodeName, taint); !has {
    84  		framework.ExpectNoError(err)
    85  		framework.Failf("Failed to find taint %s on node %s", taint.ToString(), nodeName)
    86  	}
    87  }
    88  
    89  // NodeHasTaint returns true if the node has the given taint, else returns false.
    90  func NodeHasTaint(ctx context.Context, c clientset.Interface, nodeName string, taint *v1.Taint) (bool, error) {
    91  	node, err := c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
    92  	if err != nil {
    93  		return false, err
    94  	}
    95  
    96  	nodeTaints := node.Spec.Taints
    97  
    98  	if len(nodeTaints) == 0 || !taintExists(nodeTaints, taint) {
    99  		return false, nil
   100  	}
   101  	return true, nil
   102  }
   103  
   104  // AllNodesReady checks whether all registered nodes are ready. Setting -1 on
   105  // framework.TestContext.AllowedNotReadyNodes will bypass the post test node readiness check.
   106  // TODO: we should change the AllNodesReady call in AfterEach to WaitForAllNodesHealthy,
   107  // and figure out how to do it in a configurable way, as we can't expect all setups to run
   108  // default test add-ons.
   109  func AllNodesReady(ctx context.Context, c clientset.Interface, timeout time.Duration) error {
   110  	if err := allNodesReady(ctx, c, timeout); err != nil {
   111  		return fmt.Errorf("checking for ready nodes: %w", err)
   112  	}
   113  	return nil
   114  }
   115  
   116  func allNodesReady(ctx context.Context, c clientset.Interface, timeout time.Duration) error {
   117  	if framework.TestContext.AllowedNotReadyNodes == -1 {
   118  		return nil
   119  	}
   120  
   121  	framework.Logf("Waiting up to %v for all (but %d) nodes to be ready", timeout, framework.TestContext.AllowedNotReadyNodes)
   122  
   123  	var notReady []*v1.Node
   124  	err := wait.PollUntilContextTimeout(ctx, framework.Poll, timeout, true, func(ctx context.Context) (bool, error) {
   125  		notReady = nil
   126  		// It should be OK to list unschedulable Nodes here.
   127  		nodes, err := c.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
   128  		if err != nil {
   129  			return false, err
   130  		}
   131  		for i := range nodes.Items {
   132  			node := &nodes.Items[i]
   133  			if !IsConditionSetAsExpected(node, v1.NodeReady, true) {
   134  				notReady = append(notReady, node)
   135  			}
   136  		}
   137  		// Framework allows for <TestContext.AllowedNotReadyNodes> nodes to be non-ready,
   138  		// to make it possible e.g. for incorrect deployment of some small percentage
   139  		// of nodes (which we allow in cluster validation). Some nodes that are not
   140  		// provisioned correctly at startup will never become ready (e.g. when something
   141  		// won't install correctly), so we can't expect them to be ready at any point.
   142  		return len(notReady) <= framework.TestContext.AllowedNotReadyNodes, nil
   143  	})
   144  
   145  	if err != nil && !wait.Interrupted(err) {
   146  		return err
   147  	}
   148  
   149  	if len(notReady) > framework.TestContext.AllowedNotReadyNodes {
   150  		msg := ""
   151  		for _, node := range notReady {
   152  			msg = fmt.Sprintf("%s, %s", msg, node.Name)
   153  		}
   154  		return fmt.Errorf("Not ready nodes: %#v", msg)
   155  	}
   156  	return nil
   157  }
   158  
   159  // taintExists checks if the given taint exists in list of taints. Returns true if exists false otherwise.
   160  func taintExists(taints []v1.Taint, taintToFind *v1.Taint) bool {
   161  	for _, taint := range taints {
   162  		if taint.MatchTaint(taintToFind) {
   163  			return true
   164  		}
   165  	}
   166  	return false
   167  }