vsphere_volume_node_poweroff.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package vsphere
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. "github.com/onsi/ginkgo"
  19. "github.com/onsi/gomega"
  20. "github.com/vmware/govmomi/object"
  21. vimtypes "github.com/vmware/govmomi/vim25/types"
  22. appsv1 "k8s.io/api/apps/v1"
  23. v1 "k8s.io/api/core/v1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/apimachinery/pkg/util/wait"
  26. clientset "k8s.io/client-go/kubernetes"
  27. "k8s.io/kubernetes/test/e2e/framework"
  28. e2edeploy "k8s.io/kubernetes/test/e2e/framework/deployment"
  29. e2enode "k8s.io/kubernetes/test/e2e/framework/node"
  30. e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
  31. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  32. "k8s.io/kubernetes/test/e2e/storage/utils"
  33. )
  34. /*
  35. Test to verify volume status after node power off:
  36. 1. Verify the pod got provisioned on a different node with volume attached to it
  37. 2. Verify the volume is detached from the powered off node
  38. */
  39. var _ = utils.SIGDescribe("Node Poweroff [Feature:vsphere] [Slow] [Disruptive]", func() {
  40. f := framework.NewDefaultFramework("node-poweroff")
  41. var (
  42. client clientset.Interface
  43. namespace string
  44. )
  45. ginkgo.BeforeEach(func() {
  46. e2eskipper.SkipUnlessProviderIs("vsphere")
  47. Bootstrap(f)
  48. client = f.ClientSet
  49. namespace = f.Namespace.Name
  50. framework.ExpectNoError(framework.WaitForAllNodesSchedulable(client, framework.TestContext.NodeSchedulableTimeout))
  51. nodeList, err := e2enode.GetReadySchedulableNodes(f.ClientSet)
  52. framework.ExpectNoError(err)
  53. framework.ExpectEqual(len(nodeList.Items) > 1, true, "At least 2 nodes are required for this test")
  54. })
  55. /*
  56. Steps:
  57. 1. Create a StorageClass
  58. 2. Create a PVC with the StorageClass
  59. 3. Create a Deployment with 1 replica, using the PVC
  60. 4. Verify the pod got provisioned on a node
  61. 5. Verify the volume is attached to the node
  62. 6. Power off the node where pod got provisioned
  63. 7. Verify the pod got provisioned on a different node
  64. 8. Verify the volume is attached to the new node
  65. 9. Verify the volume is detached from the old node
  66. 10. Delete the Deployment and wait for the volume to be detached
  67. 11. Delete the PVC
  68. 12. Delete the StorageClass
  69. */
  70. ginkgo.It("verify volume status after node power off", func() {
  71. ginkgo.By("Creating a Storage Class")
  72. storageClassSpec := getVSphereStorageClassSpec("test-sc", nil, nil, "")
  73. storageclass, err := client.StorageV1().StorageClasses().Create(context.TODO(), storageClassSpec, metav1.CreateOptions{})
  74. framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
  75. defer client.StorageV1().StorageClasses().Delete(context.TODO(), storageclass.Name, nil)
  76. ginkgo.By("Creating PVC using the Storage Class")
  77. pvclaimSpec := getVSphereClaimSpecWithStorageClass(namespace, "1Gi", storageclass)
  78. pvclaim, err := e2epv.CreatePVC(client, namespace, pvclaimSpec)
  79. framework.ExpectNoError(err, fmt.Sprintf("Failed to create PVC with err: %v", err))
  80. defer e2epv.DeletePersistentVolumeClaim(client, pvclaim.Name, namespace)
  81. ginkgo.By("Waiting for PVC to be in bound phase")
  82. pvclaims := []*v1.PersistentVolumeClaim{pvclaim}
  83. pvs, err := e2epv.WaitForPVClaimBoundPhase(client, pvclaims, framework.ClaimProvisionTimeout)
  84. framework.ExpectNoError(err, fmt.Sprintf("Failed to wait until PVC phase set to bound: %v", err))
  85. volumePath := pvs[0].Spec.VsphereVolume.VolumePath
  86. ginkgo.By("Creating a Deployment")
  87. deployment, err := e2edeploy.CreateDeployment(client, int32(1), map[string]string{"test": "app"}, nil, namespace, pvclaims, "")
  88. framework.ExpectNoError(err, fmt.Sprintf("Failed to create Deployment with err: %v", err))
  89. defer client.AppsV1().Deployments(namespace).Delete(context.TODO(), deployment.Name, &metav1.DeleteOptions{})
  90. ginkgo.By("Get pod from the deployment")
  91. podList, err := e2edeploy.GetPodsForDeployment(client, deployment)
  92. framework.ExpectNoError(err, fmt.Sprintf("Failed to get pod from the deployment with err: %v", err))
  93. gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
  94. pod := podList.Items[0]
  95. node1 := pod.Spec.NodeName
  96. ginkgo.By(fmt.Sprintf("Verify disk is attached to the node: %v", node1))
  97. isAttached, err := diskIsAttached(volumePath, node1)
  98. framework.ExpectNoError(err)
  99. framework.ExpectEqual(isAttached, true, "Disk is not attached to the node")
  100. ginkgo.By(fmt.Sprintf("Power off the node: %v", node1))
  101. nodeInfo := TestContext.NodeMapper.GetNodeInfo(node1)
  102. vm := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
  103. ctx, cancel := context.WithCancel(context.Background())
  104. defer cancel()
  105. _, err = vm.PowerOff(ctx)
  106. framework.ExpectNoError(err)
  107. defer vm.PowerOn(ctx)
  108. err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOff)
  109. framework.ExpectNoError(err, "Unable to power off the node")
  110. // Waiting for the pod to be failed over to a different node
  111. node2, err := waitForPodToFailover(client, deployment, node1)
  112. framework.ExpectNoError(err, "Pod did not fail over to a different node")
  113. ginkgo.By(fmt.Sprintf("Waiting for disk to be attached to the new node: %v", node2))
  114. err = waitForVSphereDiskToAttach(volumePath, node2)
  115. framework.ExpectNoError(err, "Disk is not attached to the node")
  116. ginkgo.By(fmt.Sprintf("Waiting for disk to be detached from the previous node: %v", node1))
  117. err = waitForVSphereDiskToDetach(volumePath, node1)
  118. framework.ExpectNoError(err, "Disk is not detached from the node")
  119. ginkgo.By(fmt.Sprintf("Power on the previous node: %v", node1))
  120. vm.PowerOn(ctx)
  121. err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOn)
  122. framework.ExpectNoError(err, "Unable to power on the node")
  123. })
  124. })
  125. // Wait until the pod failed over to a different node, or time out after 3 minutes
  126. func waitForPodToFailover(client clientset.Interface, deployment *appsv1.Deployment, oldNode string) (string, error) {
  127. var (
  128. timeout = 3 * time.Minute
  129. pollTime = 10 * time.Second
  130. )
  131. waitErr := wait.Poll(pollTime, timeout, func() (bool, error) {
  132. currentNode, err := getNodeForDeployment(client, deployment)
  133. if err != nil {
  134. return true, err
  135. }
  136. if currentNode != oldNode {
  137. framework.Logf("The pod has been failed over from %q to %q", oldNode, currentNode)
  138. return true, nil
  139. }
  140. framework.Logf("Waiting for pod to be failed over from %q", oldNode)
  141. return false, nil
  142. })
  143. if waitErr != nil {
  144. if waitErr == wait.ErrWaitTimeout {
  145. return "", fmt.Errorf("pod has not failed over after %v: %v", timeout, waitErr)
  146. }
  147. return "", fmt.Errorf("pod did not fail over from %q: %v", oldNode, waitErr)
  148. }
  149. return getNodeForDeployment(client, deployment)
  150. }
  151. // getNodeForDeployment returns node name for the Deployment
  152. func getNodeForDeployment(client clientset.Interface, deployment *appsv1.Deployment) (string, error) {
  153. podList, err := e2edeploy.GetPodsForDeployment(client, deployment)
  154. if err != nil {
  155. return "", err
  156. }
  157. return podList.Items[0].Spec.NodeName, nil
  158. }