sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/kubernetes/node/node.go (about)

     1  //go:build e2e
     2  // +build e2e
     3  
     4  /*
     5  Copyright 2020 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package node
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"fmt"
    26  	"log"
    27  	"strings"
    28  	"time"
    29  
    30  	. "github.com/onsi/gomega"
    31  	corev1 "k8s.io/api/core/v1"
    32  	"k8s.io/apimachinery/pkg/api/equality"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    36  	"k8s.io/client-go/kubernetes"
    37  	"sigs.k8s.io/cluster-api-provider-azure/test/e2e/kubernetes/windows"
    38  )
    39  
    40  const (
    41  	nodeOperationTimeout             = 30 * time.Second
    42  	nodeOperationSleepBetweenRetries = 3 * time.Second
    43  )
    44  
    45  func GetWindowsVersion(ctx context.Context, clientset *kubernetes.Clientset) (windows.OSVersion, error) {
    46  	options := metav1.ListOptions{
    47  		LabelSelector: "kubernetes.io/os=windows",
    48  	}
    49  	var result *corev1.NodeList
    50  	Eventually(func(g Gomega) {
    51  		var err error
    52  		result, err = clientset.CoreV1().Nodes().List(ctx, options)
    53  		g.Expect(err).NotTo(HaveOccurred())
    54  		g.Expect(result.Items).NotTo(BeEmpty())
    55  	}, nodeOperationTimeout, nodeOperationSleepBetweenRetries).Should(Succeed())
    56  
    57  	kernalVersion := result.Items[0].Status.NodeInfo.KernelVersion
    58  	kernalVersions := strings.Split(kernalVersion, ".")
    59  	if len(kernalVersions) != 4 {
    60  		return windows.Unknown, fmt.Errorf("not a valid Windows kernel version: %s", kernalVersion)
    61  	}
    62  
    63  	switch kernalVersions[2] {
    64  	case "17763":
    65  		return windows.LTSC2019, nil
    66  	default:
    67  		return windows.LTSC2019, nil
    68  	}
    69  }
    70  
    71  func TaintNode(clientset *kubernetes.Clientset, options metav1.ListOptions, taint *corev1.Taint) error {
    72  	var result *corev1.NodeList
    73  	Eventually(func(g Gomega) {
    74  		var err error
    75  		result, err = clientset.CoreV1().Nodes().List(context.Background(), options)
    76  		g.Expect(err).NotTo(HaveOccurred())
    77  		g.Expect(result.Items).NotTo(BeEmpty())
    78  	}, nodeOperationTimeout, nodeOperationSleepBetweenRetries).Should(Succeed())
    79  
    80  	for i := range result.Items {
    81  		newNode, needsUpdate := addOrUpdateTaint(&result.Items[i], taint)
    82  		if !needsUpdate {
    83  			continue
    84  		}
    85  
    86  		err := PatchNodeTaints(clientset, newNode.Name, &result.Items[i], newNode)
    87  		if err != nil {
    88  			return err
    89  		}
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  // PatchNodeTaints is taken from https://github.com/kubernetes/kubernetes/blob/v1.21.1/staging/src/k8s.io/cloud-provider/node/helpers/taints.go#L91
    96  func PatchNodeTaints(clientset *kubernetes.Clientset, nodeName string, oldNode *corev1.Node, newNode *corev1.Node) error {
    97  	oldData, err := json.Marshal(oldNode)
    98  	if err != nil {
    99  		return fmt.Errorf("failed to marshal old node %#v for node %q: %w", oldNode, nodeName, err)
   100  	}
   101  
   102  	newTaints := newNode.Spec.Taints
   103  	newNodeClone := oldNode.DeepCopy()
   104  	newNodeClone.Spec.Taints = newTaints
   105  	newData, err := json.Marshal(newNodeClone)
   106  	if err != nil {
   107  		return fmt.Errorf("failed to marshal new node %#v for node %q: %w", newNodeClone, nodeName, err)
   108  	}
   109  
   110  	patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, corev1.Node{})
   111  	if err != nil {
   112  		return fmt.Errorf("failed to create patch for node %q: %w", nodeName, err)
   113  	}
   114  
   115  	Eventually(func(g Gomega) {
   116  		_, err := clientset.CoreV1().Nodes().Patch(context.Background(), nodeName, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
   117  		if err != nil {
   118  			log.Printf("Error updating node taints on node %s:%s\n", nodeName, err.Error())
   119  		}
   120  		g.Expect(err).NotTo(HaveOccurred())
   121  	}, nodeOperationTimeout, nodeOperationSleepBetweenRetries).Should(Succeed())
   122  	return err
   123  }
   124  
   125  // From https://github.com/kubernetes/kubernetes/blob/v1.21.1/staging/src/k8s.io/cloud-provider/node/helpers/taints.go#L116
   126  // addOrUpdateTaint tries to add a taint to annotations list. Returns a new copy of updated Node and true if something was updated
   127  // false otherwise.
   128  func addOrUpdateTaint(node *corev1.Node, taint *corev1.Taint) (*corev1.Node, bool) {
   129  	newNode := node.DeepCopy()
   130  	nodeTaints := newNode.Spec.Taints
   131  
   132  	var newTaints []corev1.Taint
   133  	updated := false
   134  	for i := range nodeTaints {
   135  		if taint.MatchTaint(&nodeTaints[i]) {
   136  			if equality.Semantic.DeepEqual(*taint, nodeTaints[i]) {
   137  				return newNode, false
   138  			}
   139  			newTaints = append(newTaints, *taint)
   140  			updated = true
   141  			continue
   142  		}
   143  
   144  		newTaints = append(newTaints, nodeTaints[i])
   145  	}
   146  
   147  	if !updated {
   148  		newTaints = append(newTaints, *taint)
   149  	}
   150  
   151  	newNode.Spec.Taints = newTaints
   152  	return newNode, true
   153  }