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 })