k8s.io/kubernetes@v1.29.3/test/integration/framework/util.go (about)

     1  /*
     2  Copyright 2017 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  // TODO: This file can potentially be moved to a common place used by both e2e and integration tests.
    18  
    19  package framework
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"testing"
    25  	"time"
    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  	"k8s.io/klog/v2"
    32  	nodectlr "k8s.io/kubernetes/pkg/controller/nodelifecycle"
    33  )
    34  
    35  const (
    36  	// poll is how often to Poll pods, nodes and claims.
    37  	poll = 2 * time.Second
    38  
    39  	// singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
    40  	// transient failures from failing tests.
    41  	singleCallTimeout = 5 * time.Minute
    42  )
    43  
    44  // CreateNamespaceOrDie creates a namespace.
    45  func CreateNamespaceOrDie(c clientset.Interface, baseName string, t testing.TB) *v1.Namespace {
    46  	ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: baseName}}
    47  	result, err := c.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
    48  	if err != nil {
    49  		t.Fatalf("Failed to create namespace: %v", err)
    50  	}
    51  	return result
    52  }
    53  
    54  // DeleteNamespaceOrDie deletes a namespace.
    55  func DeleteNamespaceOrDie(c clientset.Interface, ns *v1.Namespace, t testing.TB) {
    56  	err := c.CoreV1().Namespaces().Delete(context.TODO(), ns.Name, metav1.DeleteOptions{})
    57  	if err != nil {
    58  		t.Fatalf("Failed to delete namespace: %v", err)
    59  	}
    60  }
    61  
    62  // waitListAllNodes is a wrapper around listing nodes supporting retries.
    63  func waitListAllNodes(c clientset.Interface) (*v1.NodeList, error) {
    64  	var nodes *v1.NodeList
    65  	var err error
    66  	if wait.PollImmediate(poll, singleCallTimeout, func() (bool, error) {
    67  		nodes, err = c.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
    68  		if err != nil {
    69  			return false, err
    70  		}
    71  		return true, nil
    72  	}) != nil {
    73  		return nodes, err
    74  	}
    75  	return nodes, nil
    76  }
    77  
    78  // Filter filters nodes in NodeList in place, removing nodes that do not
    79  // satisfy the given condition
    80  func Filter(nodeList *v1.NodeList, fn func(node v1.Node) bool) {
    81  	var l []v1.Node
    82  
    83  	for _, node := range nodeList.Items {
    84  		if fn(node) {
    85  			l = append(l, node)
    86  		}
    87  	}
    88  	nodeList.Items = l
    89  }
    90  
    91  // IsNodeSchedulable returns true if:
    92  // 1) doesn't have "unschedulable" field set
    93  // 2) it also returns true from IsNodeReady
    94  func IsNodeSchedulable(node *v1.Node) bool {
    95  	if node == nil {
    96  		return false
    97  	}
    98  	return !node.Spec.Unschedulable && IsNodeReady(node)
    99  }
   100  
   101  // IsNodeReady returns true if:
   102  // 1) it's Ready condition is set to true
   103  // 2) doesn't have NetworkUnavailable condition set to true
   104  func IsNodeReady(node *v1.Node) bool {
   105  	nodeReady := IsConditionSetAsExpected(node, v1.NodeReady, true)
   106  	networkReady := isConditionUnset(node, v1.NodeNetworkUnavailable) ||
   107  		IsConditionSetAsExpectedSilent(node, v1.NodeNetworkUnavailable, false)
   108  	return nodeReady && networkReady
   109  }
   110  
   111  // IsConditionSetAsExpected returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue with detailed logging.
   112  func IsConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, wantTrue bool) bool {
   113  	return isNodeConditionSetAsExpected(node, conditionType, wantTrue, false)
   114  }
   115  
   116  // IsConditionSetAsExpectedSilent returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue.
   117  func IsConditionSetAsExpectedSilent(node *v1.Node, conditionType v1.NodeConditionType, wantTrue bool) bool {
   118  	return isNodeConditionSetAsExpected(node, conditionType, wantTrue, true)
   119  }
   120  
   121  // isConditionUnset returns true if conditions of the given node do not have a match to the given conditionType, otherwise false.
   122  func isConditionUnset(node *v1.Node, conditionType v1.NodeConditionType) bool {
   123  	for _, cond := range node.Status.Conditions {
   124  		if cond.Type == conditionType {
   125  			return false
   126  		}
   127  	}
   128  	return true
   129  }
   130  
   131  // isNodeConditionSetAsExpected checks a node for a condition, and returns 'true' if the wanted value is the same as the condition value, useful for polling until a condition on a node is met.
   132  func isNodeConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, wantTrue, silent bool) bool {
   133  	// Check the node readiness condition (logging all).
   134  	for _, cond := range node.Status.Conditions {
   135  		// Ensure that the condition type and the status matches as desired.
   136  		if cond.Type == conditionType {
   137  			// For NodeReady condition we need to check Taints as well
   138  			if cond.Type == v1.NodeReady {
   139  				hasNodeControllerTaints := false
   140  				// For NodeReady we need to check if Taints are gone as well
   141  				taints := node.Spec.Taints
   142  				for _, taint := range taints {
   143  					if taint.MatchTaint(nodectlr.UnreachableTaintTemplate) || taint.MatchTaint(nodectlr.NotReadyTaintTemplate) {
   144  						hasNodeControllerTaints = true
   145  						break
   146  					}
   147  				}
   148  				if wantTrue {
   149  					if (cond.Status == v1.ConditionTrue) && !hasNodeControllerTaints {
   150  						return true
   151  					}
   152  					msg := ""
   153  					if !hasNodeControllerTaints {
   154  						msg = fmt.Sprintf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
   155  							conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
   156  					} else {
   157  						msg = fmt.Sprintf("Condition %s of node %s is %v, but Node is tainted by NodeController with %v. Failure",
   158  							conditionType, node.Name, cond.Status == v1.ConditionTrue, taints)
   159  					}
   160  					if !silent {
   161  						klog.Infof(msg)
   162  					}
   163  					return false
   164  				}
   165  				// TODO: check if the Node is tainted once we enable NC notReady/unreachable taints by default
   166  				if cond.Status != v1.ConditionTrue {
   167  					return true
   168  				}
   169  				if !silent {
   170  					klog.Infof("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
   171  						conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
   172  				}
   173  				return false
   174  			}
   175  			if (wantTrue && (cond.Status == v1.ConditionTrue)) || (!wantTrue && (cond.Status != v1.ConditionTrue)) {
   176  				return true
   177  			}
   178  			if !silent {
   179  				klog.Infof("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
   180  					conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
   181  			}
   182  			return false
   183  		}
   184  
   185  	}
   186  	if !silent {
   187  		klog.Infof("Couldn't find condition %v on node %v", conditionType, node.Name)
   188  	}
   189  	return false
   190  }