k8s.io/kubernetes@v1.29.3/test/e2e/storage/vsphere/vsphere_volume_master_restart.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  package vsphere
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/onsi/ginkgo/v2"
    27  	"github.com/onsi/gomega"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/util/uuid"
    32  	clientset "k8s.io/client-go/kubernetes"
    33  	"k8s.io/kubernetes/pkg/cluster/ports"
    34  	"k8s.io/kubernetes/test/e2e/feature"
    35  	"k8s.io/kubernetes/test/e2e/framework"
    36  	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
    37  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    38  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    39  	e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
    40  	"k8s.io/kubernetes/test/e2e/storage/utils"
    41  	admissionapi "k8s.io/pod-security-admission/api"
    42  )
    43  
    44  // waitForKubeletUp waits for the kubelet on the given host to be up.
    45  func waitForKubeletUp(ctx context.Context, host string) error {
    46  	cmd := "curl http://localhost:" + strconv.Itoa(ports.KubeletReadOnlyPort) + "/healthz"
    47  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
    48  		result, err := e2essh.SSH(ctx, cmd, host, framework.TestContext.Provider)
    49  		if err != nil || result.Code != 0 {
    50  			e2essh.LogResult(result)
    51  		}
    52  		if result.Stdout == "ok" {
    53  			return nil
    54  		}
    55  	}
    56  	return fmt.Errorf("waiting for kubelet timed out")
    57  }
    58  
    59  // restartKubelet restarts kubelet on the given host.
    60  func restartKubelet(ctx context.Context, host string) error {
    61  	var cmd string
    62  
    63  	var sudoPresent bool
    64  	sshResult, err := e2essh.SSH(ctx, "sudo --version", host, framework.TestContext.Provider)
    65  	if err != nil {
    66  		return fmt.Errorf("Unable to ssh to host %s with error %v", host, err)
    67  	}
    68  	if !strings.Contains(sshResult.Stderr, "command not found") {
    69  		sudoPresent = true
    70  	}
    71  	sshResult, err = e2essh.SSH(ctx, "systemctl --version", host, framework.TestContext.Provider)
    72  	if err != nil {
    73  		return fmt.Errorf("Failed to execute command 'systemctl' on host %s with error %v", host, err)
    74  	}
    75  	if !strings.Contains(sshResult.Stderr, "command not found") {
    76  		cmd = "systemctl restart kubelet"
    77  	} else {
    78  		cmd = "service kubelet restart"
    79  	}
    80  	if sudoPresent {
    81  		cmd = fmt.Sprintf("sudo %s", cmd)
    82  	}
    83  
    84  	framework.Logf("Restarting kubelet via ssh on host %s with command %s", host, cmd)
    85  	result, err := e2essh.SSH(ctx, cmd, host, framework.TestContext.Provider)
    86  	if err != nil || result.Code != 0 {
    87  		e2essh.LogResult(result)
    88  		return fmt.Errorf("couldn't restart kubelet: %w", err)
    89  	}
    90  	return nil
    91  }
    92  
    93  /*
    94  Test to verify volume remains attached after kubelet restart on master node
    95  For the number of schedulable nodes,
    96  1. Create a volume with default volume options
    97  2. Create a Pod
    98  3. Verify the volume is attached
    99  4. Restart the kubelet on master node
   100  5. Verify again that the volume is attached
   101  6. Delete the pod and wait for the volume to be detached
   102  7. Delete the volume
   103  */
   104  var _ = utils.SIGDescribe("Volume Attach Verify", feature.Vsphere, framework.WithSerial(), framework.WithDisruptive(), func() {
   105  	f := framework.NewDefaultFramework("restart-master")
   106  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
   107  
   108  	const labelKey = "vsphere_e2e_label"
   109  	var (
   110  		client                clientset.Interface
   111  		namespace             string
   112  		volumePaths           []string
   113  		pods                  []*v1.Pod
   114  		numNodes              int
   115  		nodeKeyValueLabelList []map[string]string
   116  		nodeNameList          []string
   117  		nodeInfo              *NodeInfo
   118  	)
   119  	ginkgo.BeforeEach(func(ctx context.Context) {
   120  		e2eskipper.SkipUnlessProviderIs("vsphere")
   121  		Bootstrap(f)
   122  		client = f.ClientSet
   123  		namespace = f.Namespace.Name
   124  		framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, client, f.Timeouts.NodeSchedulable))
   125  
   126  		nodes, err := e2enode.GetReadySchedulableNodes(ctx, client)
   127  		framework.ExpectNoError(err)
   128  		numNodes = len(nodes.Items)
   129  		if numNodes < 2 {
   130  			e2eskipper.Skipf("Requires at least %d nodes (not %d)", 2, len(nodes.Items))
   131  		}
   132  		nodeInfo = TestContext.NodeMapper.GetNodeInfo(nodes.Items[0].Name)
   133  		for i := 0; i < numNodes; i++ {
   134  			nodeName := nodes.Items[i].Name
   135  			nodeNameList = append(nodeNameList, nodeName)
   136  			nodeLabelValue := "vsphere_e2e_" + string(uuid.NewUUID())
   137  			nodeKeyValueLabel := make(map[string]string)
   138  			nodeKeyValueLabel[labelKey] = nodeLabelValue
   139  			nodeKeyValueLabelList = append(nodeKeyValueLabelList, nodeKeyValueLabel)
   140  			e2enode.AddOrUpdateLabelOnNode(client, nodeName, labelKey, nodeLabelValue)
   141  		}
   142  	})
   143  
   144  	ginkgo.It("verify volume remains attached after master kubelet restart", func(ctx context.Context) {
   145  		e2eskipper.SkipUnlessSSHKeyPresent()
   146  
   147  		// Create pod on each node
   148  		for i := 0; i < numNodes; i++ {
   149  			ginkgo.By(fmt.Sprintf("%d: Creating a test vsphere volume", i))
   150  			volumePath, err := nodeInfo.VSphere.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
   151  			framework.ExpectNoError(err)
   152  			volumePaths = append(volumePaths, volumePath)
   153  
   154  			ginkgo.By(fmt.Sprintf("Creating pod %d on node %v", i, nodeNameList[i]))
   155  			podspec := getVSpherePodSpecWithVolumePaths([]string{volumePath}, nodeKeyValueLabelList[i], nil)
   156  			pod, err := client.CoreV1().Pods(namespace).Create(ctx, podspec, metav1.CreateOptions{})
   157  			framework.ExpectNoError(err)
   158  			ginkgo.DeferCleanup(e2epod.DeletePodWithWait, client, pod)
   159  
   160  			ginkgo.By("Waiting for pod to be ready")
   161  			gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed())
   162  
   163  			pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{})
   164  			framework.ExpectNoError(err)
   165  
   166  			pods = append(pods, pod)
   167  
   168  			nodeName := pod.Spec.NodeName
   169  			ginkgo.By(fmt.Sprintf("Verify volume %s is attached to the node %s", volumePath, nodeName))
   170  			expectVolumeToBeAttached(ctx, nodeName, volumePath)
   171  		}
   172  
   173  		ginkgo.By("Restarting kubelet on instance node")
   174  		instanceAddress := framework.APIAddress() + ":22"
   175  		err := restartKubelet(ctx, instanceAddress)
   176  		framework.ExpectNoError(err, "Unable to restart kubelet on instance node")
   177  
   178  		ginkgo.By("Verifying the kubelet on instance node is up")
   179  		err = waitForKubeletUp(ctx, instanceAddress)
   180  		framework.ExpectNoError(err)
   181  
   182  		for i, pod := range pods {
   183  			volumePath := volumePaths[i]
   184  			nodeName := pod.Spec.NodeName
   185  
   186  			ginkgo.By(fmt.Sprintf("After master restart, verify volume %v is attached to the node %v", volumePath, nodeName))
   187  			expectVolumeToBeAttached(ctx, nodeName, volumePath)
   188  
   189  			ginkgo.By(fmt.Sprintf("Deleting pod on node %s", nodeName))
   190  			err = e2epod.DeletePodWithWait(ctx, client, pod)
   191  			framework.ExpectNoError(err)
   192  
   193  			ginkgo.By(fmt.Sprintf("Waiting for volume %s to be detached from the node %s", volumePath, nodeName))
   194  			err = waitForVSphereDiskToDetach(ctx, volumePath, nodeName)
   195  			framework.ExpectNoError(err)
   196  
   197  			ginkgo.By(fmt.Sprintf("Deleting volume %s", volumePath))
   198  			err = nodeInfo.VSphere.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
   199  			framework.ExpectNoError(err)
   200  		}
   201  	})
   202  })