k8s.io/kubernetes@v1.29.3/test/e2e/framework/node/resource.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  	"encoding/json"
    22  	"fmt"
    23  	"net"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/onsi/ginkgo/v2"
    28  	"github.com/onsi/gomega"
    29  
    30  	v1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/conversion"
    34  	"k8s.io/apimachinery/pkg/fields"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/rand"
    38  	"k8s.io/apimachinery/pkg/util/sets"
    39  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    40  	"k8s.io/apimachinery/pkg/util/wait"
    41  	clientset "k8s.io/client-go/kubernetes"
    42  	clientretry "k8s.io/client-go/util/retry"
    43  	"k8s.io/kubernetes/test/e2e/framework"
    44  	netutil "k8s.io/utils/net"
    45  )
    46  
    47  const (
    48  	// poll is how often to Poll pods, nodes and claims.
    49  	poll = 2 * time.Second
    50  
    51  	// singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
    52  	// transient failures from failing tests.
    53  	singleCallTimeout = 5 * time.Minute
    54  
    55  	// ssh port
    56  	sshPort = "22"
    57  )
    58  
    59  var (
    60  	// unreachableTaintTemplate is the taint for when a node becomes unreachable.
    61  	// Copied from pkg/controller/nodelifecycle to avoid pulling extra dependencies
    62  	unreachableTaintTemplate = &v1.Taint{
    63  		Key:    v1.TaintNodeUnreachable,
    64  		Effect: v1.TaintEffectNoExecute,
    65  	}
    66  
    67  	// notReadyTaintTemplate is the taint for when a node is not ready for executing pods.
    68  	// Copied from pkg/controller/nodelifecycle to avoid pulling extra dependencies
    69  	notReadyTaintTemplate = &v1.Taint{
    70  		Key:    v1.TaintNodeNotReady,
    71  		Effect: v1.TaintEffectNoExecute,
    72  	}
    73  
    74  	// updateTaintBackOff contains the maximum retries and the wait interval between two retries.
    75  	updateTaintBackOff = wait.Backoff{
    76  		Steps:    5,
    77  		Duration: 100 * time.Millisecond,
    78  		Jitter:   1.0,
    79  	}
    80  )
    81  
    82  // PodNode is a pod-node pair indicating which node a given pod is running on
    83  type PodNode struct {
    84  	// Pod represents pod name
    85  	Pod string
    86  	// Node represents node name
    87  	Node string
    88  }
    89  
    90  // FirstAddress returns the first address of the given type of each node.
    91  func FirstAddress(nodelist *v1.NodeList, addrType v1.NodeAddressType) string {
    92  	for _, n := range nodelist.Items {
    93  		for _, addr := range n.Status.Addresses {
    94  			if addr.Type == addrType && addr.Address != "" {
    95  				return addr.Address
    96  			}
    97  		}
    98  	}
    99  	return ""
   100  }
   101  
   102  func isNodeConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, wantTrue, silent bool) bool {
   103  	// Check the node readiness condition (logging all).
   104  	for _, cond := range node.Status.Conditions {
   105  		// Ensure that the condition type and the status matches as desired.
   106  		if cond.Type == conditionType {
   107  			// For NodeReady condition we need to check Taints as well
   108  			if cond.Type == v1.NodeReady {
   109  				hasNodeControllerTaints := false
   110  				// For NodeReady we need to check if Taints are gone as well
   111  				taints := node.Spec.Taints
   112  				for _, taint := range taints {
   113  					if taint.MatchTaint(unreachableTaintTemplate) || taint.MatchTaint(notReadyTaintTemplate) {
   114  						hasNodeControllerTaints = true
   115  						break
   116  					}
   117  				}
   118  				if wantTrue {
   119  					if (cond.Status == v1.ConditionTrue) && !hasNodeControllerTaints {
   120  						return true
   121  					}
   122  					msg := ""
   123  					if !hasNodeControllerTaints {
   124  						msg = fmt.Sprintf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
   125  							conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
   126  					} else {
   127  						msg = fmt.Sprintf("Condition %s of node %s is %v, but Node is tainted by NodeController with %v. Failure",
   128  							conditionType, node.Name, cond.Status == v1.ConditionTrue, taints)
   129  					}
   130  					if !silent {
   131  						framework.Logf(msg)
   132  					}
   133  					return false
   134  				}
   135  				// TODO: check if the Node is tainted once we enable NC notReady/unreachable taints by default
   136  				if cond.Status != v1.ConditionTrue {
   137  					return true
   138  				}
   139  				if !silent {
   140  					framework.Logf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
   141  						conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
   142  				}
   143  				return false
   144  			}
   145  			if (wantTrue && (cond.Status == v1.ConditionTrue)) || (!wantTrue && (cond.Status != v1.ConditionTrue)) {
   146  				return true
   147  			}
   148  			if !silent {
   149  				framework.Logf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
   150  					conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
   151  			}
   152  			return false
   153  		}
   154  
   155  	}
   156  	if !silent {
   157  		framework.Logf("Couldn't find condition %v on node %v", conditionType, node.Name)
   158  	}
   159  	return false
   160  }
   161  
   162  // 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.
   163  func IsConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, wantTrue bool) bool {
   164  	return isNodeConditionSetAsExpected(node, conditionType, wantTrue, false)
   165  }
   166  
   167  // IsConditionSetAsExpectedSilent returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue.
   168  func IsConditionSetAsExpectedSilent(node *v1.Node, conditionType v1.NodeConditionType, wantTrue bool) bool {
   169  	return isNodeConditionSetAsExpected(node, conditionType, wantTrue, true)
   170  }
   171  
   172  // isConditionUnset returns true if conditions of the given node do not have a match to the given conditionType, otherwise false.
   173  func isConditionUnset(node *v1.Node, conditionType v1.NodeConditionType) bool {
   174  	for _, cond := range node.Status.Conditions {
   175  		if cond.Type == conditionType {
   176  			return false
   177  		}
   178  	}
   179  	return true
   180  }
   181  
   182  // Filter filters nodes in NodeList in place, removing nodes that do not
   183  // satisfy the given condition
   184  func Filter(nodeList *v1.NodeList, fn func(node v1.Node) bool) {
   185  	var l []v1.Node
   186  
   187  	for _, node := range nodeList.Items {
   188  		if fn(node) {
   189  			l = append(l, node)
   190  		}
   191  	}
   192  	nodeList.Items = l
   193  }
   194  
   195  // TotalRegistered returns number of schedulable Nodes.
   196  func TotalRegistered(ctx context.Context, c clientset.Interface) (int, error) {
   197  	nodes, err := waitListSchedulableNodes(ctx, c)
   198  	if err != nil {
   199  		framework.Logf("Failed to list nodes: %v", err)
   200  		return 0, err
   201  	}
   202  	return len(nodes.Items), nil
   203  }
   204  
   205  // TotalReady returns number of ready schedulable Nodes.
   206  func TotalReady(ctx context.Context, c clientset.Interface) (int, error) {
   207  	nodes, err := waitListSchedulableNodes(ctx, c)
   208  	if err != nil {
   209  		framework.Logf("Failed to list nodes: %v", err)
   210  		return 0, err
   211  	}
   212  
   213  	// Filter out not-ready nodes.
   214  	Filter(nodes, func(node v1.Node) bool {
   215  		return IsConditionSetAsExpected(&node, v1.NodeReady, true)
   216  	})
   217  	return len(nodes.Items), nil
   218  }
   219  
   220  // GetSSHExternalIP returns node external IP concatenated with port 22 for ssh
   221  // e.g. 1.2.3.4:22
   222  func GetSSHExternalIP(node *v1.Node) (string, error) {
   223  	framework.Logf("Getting external IP address for %s", node.Name)
   224  
   225  	for _, a := range node.Status.Addresses {
   226  		if a.Type == v1.NodeExternalIP && a.Address != "" {
   227  			return net.JoinHostPort(a.Address, sshPort), nil
   228  		}
   229  	}
   230  	return "", fmt.Errorf("Couldn't get the external IP of host %s with addresses %v", node.Name, node.Status.Addresses)
   231  }
   232  
   233  // GetSSHInternalIP returns node internal IP concatenated with port 22 for ssh
   234  func GetSSHInternalIP(node *v1.Node) (string, error) {
   235  	for _, address := range node.Status.Addresses {
   236  		if address.Type == v1.NodeInternalIP && address.Address != "" {
   237  			return net.JoinHostPort(address.Address, sshPort), nil
   238  		}
   239  	}
   240  
   241  	return "", fmt.Errorf("Couldn't get the internal IP of host %s with addresses %v", node.Name, node.Status.Addresses)
   242  }
   243  
   244  // FirstAddressByTypeAndFamily returns the first address that matches the given type and family of the list of nodes
   245  func FirstAddressByTypeAndFamily(nodelist *v1.NodeList, addrType v1.NodeAddressType, family v1.IPFamily) string {
   246  	for _, n := range nodelist.Items {
   247  		addresses := GetAddressesByTypeAndFamily(&n, addrType, family)
   248  		if len(addresses) > 0 {
   249  			return addresses[0]
   250  		}
   251  	}
   252  	return ""
   253  }
   254  
   255  // GetAddressesByTypeAndFamily returns a list of addresses of the given addressType for the given node
   256  // and filtered by IPFamily
   257  func GetAddressesByTypeAndFamily(node *v1.Node, addressType v1.NodeAddressType, family v1.IPFamily) (ips []string) {
   258  	for _, nodeAddress := range node.Status.Addresses {
   259  		if nodeAddress.Type != addressType {
   260  			continue
   261  		}
   262  		if nodeAddress.Address == "" {
   263  			continue
   264  		}
   265  		if family == v1.IPv6Protocol && netutil.IsIPv6String(nodeAddress.Address) {
   266  			ips = append(ips, nodeAddress.Address)
   267  		}
   268  		if family == v1.IPv4Protocol && !netutil.IsIPv6String(nodeAddress.Address) {
   269  			ips = append(ips, nodeAddress.Address)
   270  		}
   271  	}
   272  	return
   273  }
   274  
   275  // GetAddresses returns a list of addresses of the given addressType for the given node
   276  func GetAddresses(node *v1.Node, addressType v1.NodeAddressType) (ips []string) {
   277  	for j := range node.Status.Addresses {
   278  		nodeAddress := &node.Status.Addresses[j]
   279  		if nodeAddress.Type == addressType && nodeAddress.Address != "" {
   280  			ips = append(ips, nodeAddress.Address)
   281  		}
   282  	}
   283  	return
   284  }
   285  
   286  // CollectAddresses returns a list of addresses of the given addressType for the given list of nodes
   287  func CollectAddresses(nodes *v1.NodeList, addressType v1.NodeAddressType) []string {
   288  	ips := []string{}
   289  	for i := range nodes.Items {
   290  		ips = append(ips, GetAddresses(&nodes.Items[i], addressType)...)
   291  	}
   292  	return ips
   293  }
   294  
   295  // PickIP picks one public node IP
   296  func PickIP(ctx context.Context, c clientset.Interface) (string, error) {
   297  	publicIps, err := GetPublicIps(ctx, c)
   298  	if err != nil {
   299  		return "", fmt.Errorf("get node public IPs error: %w", err)
   300  	}
   301  	if len(publicIps) == 0 {
   302  		return "", fmt.Errorf("got unexpected number (%d) of public IPs", len(publicIps))
   303  	}
   304  	ip := publicIps[0]
   305  	return ip, nil
   306  }
   307  
   308  // GetPublicIps returns a public IP list of nodes.
   309  func GetPublicIps(ctx context.Context, c clientset.Interface) ([]string, error) {
   310  	nodes, err := GetReadySchedulableNodes(ctx, c)
   311  	if err != nil {
   312  		return nil, fmt.Errorf("get schedulable and ready nodes error: %w", err)
   313  	}
   314  	ips := CollectAddresses(nodes, v1.NodeExternalIP)
   315  	if len(ips) == 0 {
   316  		// If ExternalIP isn't set, assume the test programs can reach the InternalIP
   317  		ips = CollectAddresses(nodes, v1.NodeInternalIP)
   318  	}
   319  	return ips, nil
   320  }
   321  
   322  // GetReadySchedulableNodes addresses the common use case of getting nodes you can do work on.
   323  // 1) Needs to be schedulable.
   324  // 2) Needs to be ready.
   325  // If EITHER 1 or 2 is not true, most tests will want to ignore the node entirely.
   326  // If there are no nodes that are both ready and schedulable, this will return an error.
   327  func GetReadySchedulableNodes(ctx context.Context, c clientset.Interface) (nodes *v1.NodeList, err error) {
   328  	nodes, err = checkWaitListSchedulableNodes(ctx, c)
   329  	if err != nil {
   330  		return nil, fmt.Errorf("listing schedulable nodes error: %w", err)
   331  	}
   332  	Filter(nodes, func(node v1.Node) bool {
   333  		return IsNodeSchedulable(&node) && isNodeUntainted(&node)
   334  	})
   335  	if len(nodes.Items) == 0 {
   336  		return nil, fmt.Errorf("there are currently no ready, schedulable nodes in the cluster")
   337  	}
   338  	return nodes, nil
   339  }
   340  
   341  // GetBoundedReadySchedulableNodes is like GetReadySchedulableNodes except that it returns
   342  // at most maxNodes nodes. Use this to keep your test case from blowing up when run on a
   343  // large cluster.
   344  func GetBoundedReadySchedulableNodes(ctx context.Context, c clientset.Interface, maxNodes int) (nodes *v1.NodeList, err error) {
   345  	nodes, err = GetReadySchedulableNodes(ctx, c)
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  	if len(nodes.Items) > maxNodes {
   350  		shuffled := make([]v1.Node, maxNodes)
   351  		perm := rand.Perm(len(nodes.Items))
   352  		for i, j := range perm {
   353  			if j < len(shuffled) {
   354  				shuffled[j] = nodes.Items[i]
   355  			}
   356  		}
   357  		nodes.Items = shuffled
   358  	}
   359  	return nodes, nil
   360  }
   361  
   362  // GetRandomReadySchedulableNode gets a single randomly-selected node which is available for
   363  // running pods on. If there are no available nodes it will return an error.
   364  func GetRandomReadySchedulableNode(ctx context.Context, c clientset.Interface) (*v1.Node, error) {
   365  	nodes, err := GetReadySchedulableNodes(ctx, c)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  	return &nodes.Items[rand.Intn(len(nodes.Items))], nil
   370  }
   371  
   372  // GetReadyNodesIncludingTainted returns all ready nodes, even those which are tainted.
   373  // There are cases when we care about tainted nodes
   374  // E.g. in tests related to nodes with gpu we care about nodes despite
   375  // presence of nvidia.com/gpu=present:NoSchedule taint
   376  func GetReadyNodesIncludingTainted(ctx context.Context, c clientset.Interface) (nodes *v1.NodeList, err error) {
   377  	nodes, err = checkWaitListSchedulableNodes(ctx, c)
   378  	if err != nil {
   379  		return nil, fmt.Errorf("listing schedulable nodes error: %w", err)
   380  	}
   381  	Filter(nodes, func(node v1.Node) bool {
   382  		return IsNodeSchedulable(&node)
   383  	})
   384  	return nodes, nil
   385  }
   386  
   387  // isNodeUntainted tests whether a fake pod can be scheduled on "node", given its current taints.
   388  // TODO: need to discuss wether to return bool and error type
   389  func isNodeUntainted(node *v1.Node) bool {
   390  	return isNodeUntaintedWithNonblocking(node, "")
   391  }
   392  
   393  // isNodeUntaintedWithNonblocking tests whether a fake pod can be scheduled on "node"
   394  // but allows for taints in the list of non-blocking taints.
   395  func isNodeUntaintedWithNonblocking(node *v1.Node, nonblockingTaints string) bool {
   396  	// Simple lookup for nonblocking taints based on comma-delimited list.
   397  	nonblockingTaintsMap := map[string]struct{}{}
   398  	for _, t := range strings.Split(nonblockingTaints, ",") {
   399  		if strings.TrimSpace(t) != "" {
   400  			nonblockingTaintsMap[strings.TrimSpace(t)] = struct{}{}
   401  		}
   402  	}
   403  
   404  	n := node
   405  	if len(nonblockingTaintsMap) > 0 {
   406  		nodeCopy := node.DeepCopy()
   407  		nodeCopy.Spec.Taints = []v1.Taint{}
   408  		for _, v := range node.Spec.Taints {
   409  			if _, isNonblockingTaint := nonblockingTaintsMap[v.Key]; !isNonblockingTaint {
   410  				nodeCopy.Spec.Taints = append(nodeCopy.Spec.Taints, v)
   411  			}
   412  		}
   413  		n = nodeCopy
   414  	}
   415  
   416  	return toleratesTaintsWithNoScheduleNoExecuteEffects(n.Spec.Taints, nil)
   417  }
   418  
   419  func toleratesTaintsWithNoScheduleNoExecuteEffects(taints []v1.Taint, tolerations []v1.Toleration) bool {
   420  	filteredTaints := []v1.Taint{}
   421  	for _, taint := range taints {
   422  		if taint.Effect == v1.TaintEffectNoExecute || taint.Effect == v1.TaintEffectNoSchedule {
   423  			filteredTaints = append(filteredTaints, taint)
   424  		}
   425  	}
   426  
   427  	toleratesTaint := func(taint v1.Taint) bool {
   428  		for _, toleration := range tolerations {
   429  			if toleration.ToleratesTaint(&taint) {
   430  				return true
   431  			}
   432  		}
   433  
   434  		return false
   435  	}
   436  
   437  	for _, taint := range filteredTaints {
   438  		if !toleratesTaint(taint) {
   439  			return false
   440  		}
   441  	}
   442  
   443  	return true
   444  }
   445  
   446  // IsNodeSchedulable returns true if:
   447  // 1) doesn't have "unschedulable" field set
   448  // 2) it also returns true from IsNodeReady
   449  func IsNodeSchedulable(node *v1.Node) bool {
   450  	if node == nil {
   451  		return false
   452  	}
   453  	return !node.Spec.Unschedulable && IsNodeReady(node)
   454  }
   455  
   456  // IsNodeReady returns true if:
   457  // 1) it's Ready condition is set to true
   458  // 2) doesn't have NetworkUnavailable condition set to true
   459  func IsNodeReady(node *v1.Node) bool {
   460  	nodeReady := IsConditionSetAsExpected(node, v1.NodeReady, true)
   461  	networkReady := isConditionUnset(node, v1.NodeNetworkUnavailable) ||
   462  		IsConditionSetAsExpectedSilent(node, v1.NodeNetworkUnavailable, false)
   463  	return nodeReady && networkReady
   464  }
   465  
   466  // isNodeSchedulableWithoutTaints returns true if:
   467  // 1) doesn't have "unschedulable" field set
   468  // 2) it also returns true from IsNodeReady
   469  // 3) it also returns true from isNodeUntainted
   470  func isNodeSchedulableWithoutTaints(node *v1.Node) bool {
   471  	return IsNodeSchedulable(node) && isNodeUntainted(node)
   472  }
   473  
   474  // hasNonblockingTaint returns true if the node contains at least
   475  // one taint with a key matching the regexp.
   476  func hasNonblockingTaint(node *v1.Node, nonblockingTaints string) bool {
   477  	if node == nil {
   478  		return false
   479  	}
   480  
   481  	// Simple lookup for nonblocking taints based on comma-delimited list.
   482  	nonblockingTaintsMap := map[string]struct{}{}
   483  	for _, t := range strings.Split(nonblockingTaints, ",") {
   484  		if strings.TrimSpace(t) != "" {
   485  			nonblockingTaintsMap[strings.TrimSpace(t)] = struct{}{}
   486  		}
   487  	}
   488  
   489  	for _, taint := range node.Spec.Taints {
   490  		if _, hasNonblockingTaint := nonblockingTaintsMap[taint.Key]; hasNonblockingTaint {
   491  			return true
   492  		}
   493  	}
   494  
   495  	return false
   496  }
   497  
   498  // PodNodePairs return podNode pairs for all pods in a namespace
   499  func PodNodePairs(ctx context.Context, c clientset.Interface, ns string) ([]PodNode, error) {
   500  	var result []PodNode
   501  
   502  	podList, err := c.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{})
   503  	if err != nil {
   504  		return result, err
   505  	}
   506  
   507  	for _, pod := range podList.Items {
   508  		result = append(result, PodNode{
   509  			Pod:  pod.Name,
   510  			Node: pod.Spec.NodeName,
   511  		})
   512  	}
   513  
   514  	return result, nil
   515  }
   516  
   517  // GetClusterZones returns the values of zone label collected from all nodes.
   518  func GetClusterZones(ctx context.Context, c clientset.Interface) (sets.String, error) {
   519  	nodes, err := c.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
   520  	if err != nil {
   521  		return nil, fmt.Errorf("Error getting nodes while attempting to list cluster zones: %w", err)
   522  	}
   523  
   524  	// collect values of zone label from all nodes
   525  	zones := sets.NewString()
   526  	for _, node := range nodes.Items {
   527  		if zone, found := node.Labels[v1.LabelFailureDomainBetaZone]; found {
   528  			zones.Insert(zone)
   529  		}
   530  
   531  		if zone, found := node.Labels[v1.LabelTopologyZone]; found {
   532  			zones.Insert(zone)
   533  		}
   534  	}
   535  	return zones, nil
   536  }
   537  
   538  // GetSchedulableClusterZones returns the values of zone label collected from all nodes which are schedulable.
   539  func GetSchedulableClusterZones(ctx context.Context, c clientset.Interface) (sets.Set[string], error) {
   540  	// GetReadySchedulableNodes already filters our tainted and unschedulable nodes.
   541  	nodes, err := GetReadySchedulableNodes(ctx, c)
   542  	if err != nil {
   543  		return nil, fmt.Errorf("error getting nodes while attempting to list cluster zones: %w", err)
   544  	}
   545  
   546  	// collect values of zone label from all nodes
   547  	zones := sets.New[string]()
   548  	for _, node := range nodes.Items {
   549  		if zone, found := node.Labels[v1.LabelFailureDomainBetaZone]; found {
   550  			zones.Insert(zone)
   551  		}
   552  
   553  		if zone, found := node.Labels[v1.LabelTopologyZone]; found {
   554  			zones.Insert(zone)
   555  		}
   556  	}
   557  	return zones, nil
   558  }
   559  
   560  // CreatePodsPerNodeForSimpleApp creates pods w/ labels.  Useful for tests which make a bunch of pods w/o any networking.
   561  func CreatePodsPerNodeForSimpleApp(ctx context.Context, c clientset.Interface, namespace, appName string, podSpec func(n v1.Node) v1.PodSpec, maxCount int) map[string]string {
   562  	nodes, err := GetBoundedReadySchedulableNodes(ctx, c, maxCount)
   563  	// TODO use wrapper methods in expect.go after removing core e2e dependency on node
   564  	gomega.ExpectWithOffset(2, err).NotTo(gomega.HaveOccurred())
   565  	podLabels := map[string]string{
   566  		"app": appName + "-pod",
   567  	}
   568  	for i, node := range nodes.Items {
   569  		framework.Logf("%v/%v : Creating container with label app=%v-pod", i, maxCount, appName)
   570  		_, err := c.CoreV1().Pods(namespace).Create(ctx, &v1.Pod{
   571  			ObjectMeta: metav1.ObjectMeta{
   572  				Name:   fmt.Sprintf(appName+"-pod-%v", i),
   573  				Labels: podLabels,
   574  			},
   575  			Spec: podSpec(node),
   576  		}, metav1.CreateOptions{})
   577  		// TODO use wrapper methods in expect.go after removing core e2e dependency on node
   578  		gomega.ExpectWithOffset(2, err).NotTo(gomega.HaveOccurred())
   579  	}
   580  	return podLabels
   581  }
   582  
   583  // RemoveTaintsOffNode removes a list of taints from the given node
   584  // It is simply a helper wrapper for RemoveTaintOffNode
   585  func RemoveTaintsOffNode(ctx context.Context, c clientset.Interface, nodeName string, taints []v1.Taint) {
   586  	for _, taint := range taints {
   587  		RemoveTaintOffNode(ctx, c, nodeName, taint)
   588  	}
   589  }
   590  
   591  // RemoveTaintOffNode removes the given taint from the given node.
   592  func RemoveTaintOffNode(ctx context.Context, c clientset.Interface, nodeName string, taint v1.Taint) {
   593  	err := removeNodeTaint(ctx, c, nodeName, nil, &taint)
   594  
   595  	// TODO use wrapper methods in expect.go after removing core e2e dependency on node
   596  	gomega.ExpectWithOffset(2, err).NotTo(gomega.HaveOccurred())
   597  	verifyThatTaintIsGone(ctx, c, nodeName, &taint)
   598  }
   599  
   600  // AddOrUpdateTaintOnNode adds the given taint to the given node or updates taint.
   601  func AddOrUpdateTaintOnNode(ctx context.Context, c clientset.Interface, nodeName string, taint v1.Taint) {
   602  	// TODO use wrapper methods in expect.go after removing the dependency on this
   603  	// package from the core e2e framework.
   604  	err := addOrUpdateTaintOnNode(ctx, c, nodeName, &taint)
   605  	gomega.ExpectWithOffset(2, err).NotTo(gomega.HaveOccurred())
   606  }
   607  
   608  // addOrUpdateTaintOnNode add taints to the node. If taint was added into node, it'll issue API calls
   609  // to update nodes; otherwise, no API calls. Return error if any.
   610  // copied from pkg/controller/controller_utils.go AddOrUpdateTaintOnNode()
   611  func addOrUpdateTaintOnNode(ctx context.Context, c clientset.Interface, nodeName string, taints ...*v1.Taint) error {
   612  	if len(taints) == 0 {
   613  		return nil
   614  	}
   615  	firstTry := true
   616  	return clientretry.RetryOnConflict(updateTaintBackOff, func() error {
   617  		var err error
   618  		var oldNode *v1.Node
   619  		// First we try getting node from the API server cache, as it's cheaper. If it fails
   620  		// we get it from etcd to be sure to have fresh data.
   621  		if firstTry {
   622  			oldNode, err = c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{ResourceVersion: "0"})
   623  			firstTry = false
   624  		} else {
   625  			oldNode, err = c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
   626  		}
   627  		if err != nil {
   628  			return err
   629  		}
   630  
   631  		var newNode *v1.Node
   632  		oldNodeCopy := oldNode
   633  		updated := false
   634  		for _, taint := range taints {
   635  			curNewNode, ok, err := addOrUpdateTaint(oldNodeCopy, taint)
   636  			if err != nil {
   637  				return fmt.Errorf("failed to update taint of node")
   638  			}
   639  			updated = updated || ok
   640  			newNode = curNewNode
   641  			oldNodeCopy = curNewNode
   642  		}
   643  		if !updated {
   644  			return nil
   645  		}
   646  		return patchNodeTaints(ctx, c, nodeName, oldNode, newNode)
   647  	})
   648  }
   649  
   650  // addOrUpdateTaint tries to add a taint to annotations list. Returns a new copy of updated Node and true if something was updated
   651  // false otherwise.
   652  // copied from pkg/util/taints/taints.go AddOrUpdateTaint()
   653  func addOrUpdateTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) {
   654  	newNode := node.DeepCopy()
   655  	nodeTaints := newNode.Spec.Taints
   656  
   657  	var newTaints []v1.Taint
   658  	updated := false
   659  	for i := range nodeTaints {
   660  		if taint.MatchTaint(&nodeTaints[i]) {
   661  			if semantic.DeepEqual(*taint, nodeTaints[i]) {
   662  				return newNode, false, nil
   663  			}
   664  			newTaints = append(newTaints, *taint)
   665  			updated = true
   666  			continue
   667  		}
   668  
   669  		newTaints = append(newTaints, nodeTaints[i])
   670  	}
   671  
   672  	if !updated {
   673  		newTaints = append(newTaints, *taint)
   674  	}
   675  
   676  	newNode.Spec.Taints = newTaints
   677  	return newNode, true, nil
   678  }
   679  
   680  // semantic can do semantic deep equality checks for core objects.
   681  // Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true
   682  // copied from pkg/apis/core/helper/helpers.go Semantic
   683  var semantic = conversion.EqualitiesOrDie(
   684  	func(a, b resource.Quantity) bool {
   685  		// Ignore formatting, only care that numeric value stayed the same.
   686  		// TODO: if we decide it's important, it should be safe to start comparing the format.
   687  		//
   688  		// Uninitialized quantities are equivalent to 0 quantities.
   689  		return a.Cmp(b) == 0
   690  	},
   691  	func(a, b metav1.MicroTime) bool {
   692  		return a.UTC() == b.UTC()
   693  	},
   694  	func(a, b metav1.Time) bool {
   695  		return a.UTC() == b.UTC()
   696  	},
   697  	func(a, b labels.Selector) bool {
   698  		return a.String() == b.String()
   699  	},
   700  	func(a, b fields.Selector) bool {
   701  		return a.String() == b.String()
   702  	},
   703  )
   704  
   705  // removeNodeTaint is for cleaning up taints temporarily added to node,
   706  // won't fail if target taint doesn't exist or has been removed.
   707  // If passed a node it'll check if there's anything to be done, if taint is not present it won't issue
   708  // any API calls.
   709  func removeNodeTaint(ctx context.Context, c clientset.Interface, nodeName string, node *v1.Node, taints ...*v1.Taint) error {
   710  	if len(taints) == 0 {
   711  		return nil
   712  	}
   713  	// Short circuit for limiting amount of API calls.
   714  	if node != nil {
   715  		match := false
   716  		for _, taint := range taints {
   717  			if taintExists(node.Spec.Taints, taint) {
   718  				match = true
   719  				break
   720  			}
   721  		}
   722  		if !match {
   723  			return nil
   724  		}
   725  	}
   726  
   727  	firstTry := true
   728  	return clientretry.RetryOnConflict(updateTaintBackOff, func() error {
   729  		var err error
   730  		var oldNode *v1.Node
   731  		// First we try getting node from the API server cache, as it's cheaper. If it fails
   732  		// we get it from etcd to be sure to have fresh data.
   733  		if firstTry {
   734  			oldNode, err = c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{ResourceVersion: "0"})
   735  			firstTry = false
   736  		} else {
   737  			oldNode, err = c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
   738  		}
   739  		if err != nil {
   740  			return err
   741  		}
   742  
   743  		var newNode *v1.Node
   744  		oldNodeCopy := oldNode
   745  		updated := false
   746  		for _, taint := range taints {
   747  			curNewNode, ok, err := removeTaint(oldNodeCopy, taint)
   748  			if err != nil {
   749  				return fmt.Errorf("failed to remove taint of node")
   750  			}
   751  			updated = updated || ok
   752  			newNode = curNewNode
   753  			oldNodeCopy = curNewNode
   754  		}
   755  		if !updated {
   756  			return nil
   757  		}
   758  		return patchNodeTaints(ctx, c, nodeName, oldNode, newNode)
   759  	})
   760  }
   761  
   762  // patchNodeTaints patches node's taints.
   763  func patchNodeTaints(ctx context.Context, c clientset.Interface, nodeName string, oldNode *v1.Node, newNode *v1.Node) error {
   764  	oldData, err := json.Marshal(oldNode)
   765  	if err != nil {
   766  		return fmt.Errorf("failed to marshal old node %#v for node %q: %w", oldNode, nodeName, err)
   767  	}
   768  
   769  	newTaints := newNode.Spec.Taints
   770  	newNodeClone := oldNode.DeepCopy()
   771  	newNodeClone.Spec.Taints = newTaints
   772  	newData, err := json.Marshal(newNodeClone)
   773  	if err != nil {
   774  		return fmt.Errorf("failed to marshal new node %#v for node %q: %w", newNodeClone, nodeName, err)
   775  	}
   776  
   777  	patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
   778  	if err != nil {
   779  		return fmt.Errorf("failed to create patch for node %q: %w", nodeName, err)
   780  	}
   781  
   782  	_, err = c.CoreV1().Nodes().Patch(ctx, nodeName, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
   783  	return err
   784  }
   785  
   786  // removeTaint tries to remove a taint from annotations list. Returns a new copy of updated Node and true if something was updated
   787  // false otherwise.
   788  func removeTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) {
   789  	newNode := node.DeepCopy()
   790  	nodeTaints := newNode.Spec.Taints
   791  	if len(nodeTaints) == 0 {
   792  		return newNode, false, nil
   793  	}
   794  
   795  	if !taintExists(nodeTaints, taint) {
   796  		return newNode, false, nil
   797  	}
   798  
   799  	newTaints, _ := deleteTaint(nodeTaints, taint)
   800  	newNode.Spec.Taints = newTaints
   801  	return newNode, true, nil
   802  }
   803  
   804  // deleteTaint removes all the taints that have the same key and effect to given taintToDelete.
   805  func deleteTaint(taints []v1.Taint, taintToDelete *v1.Taint) ([]v1.Taint, bool) {
   806  	var newTaints []v1.Taint
   807  	deleted := false
   808  	for i := range taints {
   809  		if taintToDelete.MatchTaint(&taints[i]) {
   810  			deleted = true
   811  			continue
   812  		}
   813  		newTaints = append(newTaints, taints[i])
   814  	}
   815  	return newTaints, deleted
   816  }
   817  
   818  func verifyThatTaintIsGone(ctx context.Context, c clientset.Interface, nodeName string, taint *v1.Taint) {
   819  	ginkgo.By("verifying the node doesn't have the taint " + taint.ToString())
   820  	nodeUpdated, err := c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
   821  
   822  	// TODO use wrapper methods in expect.go after removing core e2e dependency on node
   823  	gomega.ExpectWithOffset(2, err).NotTo(gomega.HaveOccurred())
   824  	if taintExists(nodeUpdated.Spec.Taints, taint) {
   825  		framework.Failf("Failed removing taint " + taint.ToString() + " of the node " + nodeName)
   826  	}
   827  }