k8s.io/kubernetes@v1.29.3/test/e2e/storage/vsphere/vsphere_volume_placement.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 "time" 24 25 "github.com/onsi/ginkgo/v2" 26 "github.com/onsi/gomega" 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/uuid" 30 clientset "k8s.io/client-go/kubernetes" 31 "k8s.io/kubernetes/test/e2e/feature" 32 "k8s.io/kubernetes/test/e2e/framework" 33 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 34 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 35 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 36 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 37 "k8s.io/kubernetes/test/e2e/storage/utils" 38 admissionapi "k8s.io/pod-security-admission/api" 39 ) 40 41 var _ = utils.SIGDescribe("Volume Placement", feature.Vsphere, func() { 42 f := framework.NewDefaultFramework("volume-placement") 43 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 44 const ( 45 NodeLabelKey = "vsphere_e2e_label_volume_placement" 46 ) 47 var ( 48 c clientset.Interface 49 ns string 50 volumePaths []string 51 node1Name string 52 node1KeyValueLabel map[string]string 53 node2Name string 54 node2KeyValueLabel map[string]string 55 nodeInfo *NodeInfo 56 vsp *VSphere 57 ) 58 ginkgo.BeforeEach(func(ctx context.Context) { 59 e2eskipper.SkipUnlessProviderIs("vsphere") 60 Bootstrap(f) 61 c = f.ClientSet 62 ns = f.Namespace.Name 63 framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, c, f.Timeouts.NodeSchedulable)) 64 node1Name, node1KeyValueLabel, node2Name, node2KeyValueLabel = testSetupVolumePlacement(ctx, c, ns) 65 ginkgo.DeferCleanup(func() { 66 if len(node1KeyValueLabel) > 0 { 67 e2enode.RemoveLabelOffNode(c, node1Name, NodeLabelKey) 68 } 69 if len(node2KeyValueLabel) > 0 { 70 e2enode.RemoveLabelOffNode(c, node2Name, NodeLabelKey) 71 } 72 }) 73 nodeInfo = TestContext.NodeMapper.GetNodeInfo(node1Name) 74 vsp = nodeInfo.VSphere 75 ginkgo.By("creating vmdk") 76 volumePath, err := vsp.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef) 77 framework.ExpectNoError(err) 78 volumePaths = append(volumePaths, volumePath) 79 ginkgo.DeferCleanup(func() { 80 for _, volumePath := range volumePaths { 81 vsp.DeleteVolume(volumePath, nodeInfo.DataCenterRef) 82 } 83 volumePaths = nil 84 }) 85 }) 86 87 /* 88 Steps 89 90 1. Create pod Spec with volume path of the vmdk and NodeSelector set to label assigned to node1. 91 2. Create pod and wait for pod to become ready. 92 3. Verify volume is attached to the node1. 93 4. Create empty file on the volume to verify volume is writable. 94 5. Verify newly created file and previously created files exist on the volume. 95 6. Delete pod. 96 7. Wait for volume to be detached from the node1. 97 8. Repeat Step 1 to 7 and make sure back to back pod creation on same worker node with the same volume is working as expected. 98 99 */ 100 101 ginkgo.It("should create and delete pod with the same volume source on the same worker node", func(ctx context.Context) { 102 var volumeFiles []string 103 pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 104 105 // Create empty files on the mounted volumes on the pod to verify volume is writable 106 // Verify newly and previously created files present on the volume mounted on the pod 107 newEmptyFileName := fmt.Sprintf("/mnt/volume1/%v_1.txt", ns) 108 volumeFiles = append(volumeFiles, newEmptyFileName) 109 createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles) 110 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths) 111 112 ginkgo.By(fmt.Sprintf("Creating pod on the same node: %v", node1Name)) 113 pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 114 115 // Create empty files on the mounted volumes on the pod to verify volume is writable 116 // Verify newly and previously created files present on the volume mounted on the pod 117 newEmptyFileName = fmt.Sprintf("/mnt/volume1/%v_2.txt", ns) 118 volumeFiles = append(volumeFiles, newEmptyFileName) 119 createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles) 120 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths) 121 }) 122 123 /* 124 Steps 125 126 1. Create pod Spec with volume path of the vmdk1 and NodeSelector set to node1's label. 127 2. Create pod and wait for POD to become ready. 128 3. Verify volume is attached to the node1. 129 4. Create empty file on the volume to verify volume is writable. 130 5. Verify newly created file and previously created files exist on the volume. 131 6. Delete pod. 132 7. Wait for volume to be detached from the node1. 133 8. Create pod Spec with volume path of the vmdk1 and NodeSelector set to node2's label. 134 9. Create pod and wait for pod to become ready. 135 10. Verify volume is attached to the node2. 136 11. Create empty file on the volume to verify volume is writable. 137 12. Verify newly created file and previously created files exist on the volume. 138 13. Delete pod. 139 */ 140 141 ginkgo.It("should create and delete pod with the same volume source attach/detach to different worker nodes", func(ctx context.Context) { 142 var volumeFiles []string 143 pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 144 // Create empty files on the mounted volumes on the pod to verify volume is writable 145 // Verify newly and previously created files present on the volume mounted on the pod 146 newEmptyFileName := fmt.Sprintf("/mnt/volume1/%v_1.txt", ns) 147 volumeFiles = append(volumeFiles, newEmptyFileName) 148 createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles) 149 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths) 150 151 ginkgo.By(fmt.Sprintf("Creating pod on the another node: %v", node2Name)) 152 pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node2Name, node2KeyValueLabel, volumePaths) 153 154 newEmptyFileName = fmt.Sprintf("/mnt/volume1/%v_2.txt", ns) 155 volumeFiles = append(volumeFiles, newEmptyFileName) 156 // Create empty files on the mounted volumes on the pod to verify volume is writable 157 // Verify newly and previously created files present on the volume mounted on the pod 158 createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles) 159 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node2Name, volumePaths) 160 }) 161 162 /* 163 Test multiple volumes from same datastore within the same pod 164 1. Create volumes - vmdk2 165 2. Create pod Spec with volume path of vmdk1 (vmdk1 is created in test setup) and vmdk2. 166 3. Create pod using spec created in step-2 and wait for pod to become ready. 167 4. Verify both volumes are attached to the node on which pod are created. Write some data to make sure volume are accessible. 168 5. Delete pod. 169 6. Wait for vmdk1 and vmdk2 to be detached from node. 170 7. Create pod using spec created in step-2 and wait for pod to become ready. 171 8. Verify both volumes are attached to the node on which PODs are created. Verify volume contents are matching with the content written in step 4. 172 9. Delete POD. 173 10. Wait for vmdk1 and vmdk2 to be detached from node. 174 */ 175 176 ginkgo.It("should create and delete pod with multiple volumes from same datastore", func(ctx context.Context) { 177 ginkgo.By("creating another vmdk") 178 volumePath, err := vsp.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef) 179 framework.ExpectNoError(err) 180 volumePaths = append(volumePaths, volumePath) 181 182 ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume: %v and volume: %v", node1Name, volumePaths[0], volumePaths[1])) 183 pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 184 // Create empty files on the mounted volumes on the pod to verify volume is writable 185 // Verify newly and previously created files present on the volume mounted on the pod 186 volumeFiles := []string{ 187 fmt.Sprintf("/mnt/volume1/%v_1.txt", ns), 188 fmt.Sprintf("/mnt/volume2/%v_1.txt", ns), 189 } 190 createAndVerifyFilesOnVolume(ns, pod.Name, volumeFiles, volumeFiles) 191 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths) 192 ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume :%v and volume: %v", node1Name, volumePaths[0], volumePaths[1])) 193 pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 194 // Create empty files on the mounted volumes on the pod to verify volume is writable 195 // Verify newly and previously created files present on the volume mounted on the pod 196 newEmptyFilesNames := []string{ 197 fmt.Sprintf("/mnt/volume1/%v_2.txt", ns), 198 fmt.Sprintf("/mnt/volume2/%v_2.txt", ns), 199 } 200 volumeFiles = append(volumeFiles, newEmptyFilesNames[0]) 201 volumeFiles = append(volumeFiles, newEmptyFilesNames[1]) 202 createAndVerifyFilesOnVolume(ns, pod.Name, newEmptyFilesNames, volumeFiles) 203 }) 204 205 /* 206 Test multiple volumes from different datastore within the same pod 207 1. Create volumes - vmdk2 on non default shared datastore. 208 2. Create pod Spec with volume path of vmdk1 (vmdk1 is created in test setup on default datastore) and vmdk2. 209 3. Create pod using spec created in step-2 and wait for pod to become ready. 210 4. Verify both volumes are attached to the node on which pod are created. Write some data to make sure volume are accessible. 211 5. Delete pod. 212 6. Wait for vmdk1 and vmdk2 to be detached from node. 213 7. Create pod using spec created in step-2 and wait for pod to become ready. 214 8. Verify both volumes are attached to the node on which PODs are created. Verify volume contents are matching with the content written in step 4. 215 9. Delete POD. 216 10. Wait for vmdk1 and vmdk2 to be detached from node. 217 */ 218 ginkgo.It("should create and delete pod with multiple volumes from different datastore", func(ctx context.Context) { 219 ginkgo.By("creating another vmdk on non default shared datastore") 220 var volumeOptions *VolumeOptions 221 volumeOptions = new(VolumeOptions) 222 volumeOptions.CapacityKB = 2097152 223 volumeOptions.Name = "e2e-vmdk-" + strconv.FormatInt(time.Now().UnixNano(), 10) 224 volumeOptions.Datastore = GetAndExpectStringEnvVar(SecondSharedDatastore) 225 volumePath, err := vsp.CreateVolume(volumeOptions, nodeInfo.DataCenterRef) 226 227 framework.ExpectNoError(err) 228 volumePaths = append(volumePaths, volumePath) 229 230 ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume :%v and volume: %v", node1Name, volumePaths[0], volumePaths[1])) 231 pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 232 233 // Create empty files on the mounted volumes on the pod to verify volume is writable 234 // Verify newly and previously created files present on the volume mounted on the pod 235 volumeFiles := []string{ 236 fmt.Sprintf("/mnt/volume1/%v_1.txt", ns), 237 fmt.Sprintf("/mnt/volume2/%v_1.txt", ns), 238 } 239 createAndVerifyFilesOnVolume(ns, pod.Name, volumeFiles, volumeFiles) 240 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths) 241 242 ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume :%v and volume: %v", node1Name, volumePaths[0], volumePaths[1])) 243 pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths) 244 // Create empty files on the mounted volumes on the pod to verify volume is writable 245 // Verify newly and previously created files present on the volume mounted on the pod 246 newEmptyFileNames := []string{ 247 fmt.Sprintf("/mnt/volume1/%v_2.txt", ns), 248 fmt.Sprintf("/mnt/volume2/%v_2.txt", ns), 249 } 250 volumeFiles = append(volumeFiles, newEmptyFileNames[0]) 251 volumeFiles = append(volumeFiles, newEmptyFileNames[1]) 252 createAndVerifyFilesOnVolume(ns, pod.Name, newEmptyFileNames, volumeFiles) 253 deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths) 254 }) 255 256 /* 257 Test Back-to-back pod creation/deletion with different volume sources on the same worker node 258 1. Create volumes - vmdk2 259 2. Create pod Spec - pod-SpecA with volume path of vmdk1 and NodeSelector set to label assigned to node1. 260 3. Create pod Spec - pod-SpecB with volume path of vmdk2 and NodeSelector set to label assigned to node1. 261 4. Create pod-A using pod-SpecA and wait for pod to become ready. 262 5. Create pod-B using pod-SpecB and wait for POD to become ready. 263 6. Verify volumes are attached to the node. 264 7. Create empty file on the volume to make sure volume is accessible. (Perform this step on pod-A and pod-B) 265 8. Verify file created in step 5 is present on the volume. (perform this step on pod-A and pod-B) 266 9. Delete pod-A and pod-B 267 10. Repeatedly (5 times) perform step 4 to 9 and verify associated volume's content is matching. 268 11. Wait for vmdk1 and vmdk2 to be detached from node. 269 */ 270 ginkgo.It("test back to back pod creation and deletion with different volume sources on the same worker node", func(ctx context.Context) { 271 var ( 272 podA *v1.Pod 273 podB *v1.Pod 274 testvolumePathsPodA []string 275 testvolumePathsPodB []string 276 podAFiles []string 277 podBFiles []string 278 ) 279 280 defer func() { 281 ginkgo.By("clean up undeleted pods") 282 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podA), "defer: Failed to delete pod ", podA.Name) 283 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podB), "defer: Failed to delete pod ", podB.Name) 284 ginkgo.By(fmt.Sprintf("wait for volumes to be detached from the node: %v", node1Name)) 285 for _, volumePath := range volumePaths { 286 framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, volumePath, node1Name)) 287 } 288 }() 289 290 testvolumePathsPodA = append(testvolumePathsPodA, volumePaths[0]) 291 // Create another VMDK Volume 292 ginkgo.By("creating another vmdk") 293 volumePath, err := vsp.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef) 294 framework.ExpectNoError(err) 295 volumePaths = append(volumePaths, volumePath) 296 testvolumePathsPodB = append(testvolumePathsPodA, volumePath) 297 298 for index := 0; index < 5; index++ { 299 ginkgo.By(fmt.Sprintf("Creating pod-A on the node: %v with volume: %v", node1Name, testvolumePathsPodA[0])) 300 podA = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, testvolumePathsPodA) 301 302 ginkgo.By(fmt.Sprintf("Creating pod-B on the node: %v with volume: %v", node1Name, testvolumePathsPodB[0])) 303 podB = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, testvolumePathsPodB) 304 305 podAFileName := fmt.Sprintf("/mnt/volume1/podA_%v_%v.txt", ns, index+1) 306 podBFileName := fmt.Sprintf("/mnt/volume1/podB_%v_%v.txt", ns, index+1) 307 podAFiles = append(podAFiles, podAFileName) 308 podBFiles = append(podBFiles, podBFileName) 309 310 // Create empty files on the mounted volumes on the pod to verify volume is writable 311 ginkgo.By("Creating empty file on volume mounted on pod-A") 312 e2eoutput.CreateEmptyFileOnPod(ns, podA.Name, podAFileName) 313 314 ginkgo.By("Creating empty file volume mounted on pod-B") 315 e2eoutput.CreateEmptyFileOnPod(ns, podB.Name, podBFileName) 316 317 // Verify newly and previously created files present on the volume mounted on the pod 318 ginkgo.By("Verify newly Created file and previously created files present on volume mounted on pod-A") 319 verifyFilesExistOnVSphereVolume(ns, podA.Name, podAFiles...) 320 ginkgo.By("Verify newly Created file and previously created files present on volume mounted on pod-B") 321 verifyFilesExistOnVSphereVolume(ns, podB.Name, podBFiles...) 322 323 ginkgo.By("Deleting pod-A") 324 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podA), "Failed to delete pod ", podA.Name) 325 ginkgo.By("Deleting pod-B") 326 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podB), "Failed to delete pod ", podB.Name) 327 } 328 }) 329 }) 330 331 func testSetupVolumePlacement(ctx context.Context, client clientset.Interface, namespace string) (node1Name string, node1KeyValueLabel map[string]string, node2Name string, node2KeyValueLabel map[string]string) { 332 nodes, err := e2enode.GetBoundedReadySchedulableNodes(ctx, client, 2) 333 framework.ExpectNoError(err) 334 if len(nodes.Items) < 2 { 335 e2eskipper.Skipf("Requires at least %d nodes (not %d)", 2, len(nodes.Items)) 336 } 337 node1Name = nodes.Items[0].Name 338 node2Name = nodes.Items[1].Name 339 node1LabelValue := "vsphere_e2e_" + string(uuid.NewUUID()) 340 node1KeyValueLabel = make(map[string]string) 341 node1KeyValueLabel[NodeLabelKey] = node1LabelValue 342 e2enode.AddOrUpdateLabelOnNode(client, node1Name, NodeLabelKey, node1LabelValue) 343 344 node2LabelValue := "vsphere_e2e_" + string(uuid.NewUUID()) 345 node2KeyValueLabel = make(map[string]string) 346 node2KeyValueLabel[NodeLabelKey] = node2LabelValue 347 e2enode.AddOrUpdateLabelOnNode(client, node2Name, NodeLabelKey, node2LabelValue) 348 return node1Name, node1KeyValueLabel, node2Name, node2KeyValueLabel 349 } 350 351 func createPodWithVolumeAndNodeSelector(ctx context.Context, client clientset.Interface, namespace string, nodeName string, nodeKeyValueLabel map[string]string, volumePaths []string) *v1.Pod { 352 var pod *v1.Pod 353 var err error 354 ginkgo.By(fmt.Sprintf("Creating pod on the node: %v", nodeName)) 355 podspec := getVSpherePodSpecWithVolumePaths(volumePaths, nodeKeyValueLabel, nil) 356 357 pod, err = client.CoreV1().Pods(namespace).Create(ctx, podspec, metav1.CreateOptions{}) 358 framework.ExpectNoError(err) 359 ginkgo.By("Waiting for pod to be ready") 360 gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed()) 361 362 ginkgo.By(fmt.Sprintf("Verify volume is attached to the node:%v", nodeName)) 363 for _, volumePath := range volumePaths { 364 isAttached, err := diskIsAttached(ctx, volumePath, nodeName) 365 framework.ExpectNoError(err) 366 if !isAttached { 367 framework.Failf("Volume: %s is not attached to the node: %v", volumePath, nodeName) 368 } 369 } 370 return pod 371 } 372 373 func createAndVerifyFilesOnVolume(namespace string, podname string, newEmptyfilesToCreate []string, filesToCheck []string) { 374 // Create empty files on the mounted volumes on the pod to verify volume is writable 375 ginkgo.By(fmt.Sprintf("Creating empty file on volume mounted on: %v", podname)) 376 createEmptyFilesOnVSphereVolume(namespace, podname, newEmptyfilesToCreate) 377 378 // Verify newly and previously created files present on the volume mounted on the pod 379 ginkgo.By(fmt.Sprintf("Verify newly Created file and previously created files present on volume mounted on: %v", podname)) 380 verifyFilesExistOnVSphereVolume(namespace, podname, filesToCheck...) 381 } 382 383 func deletePodAndWaitForVolumeToDetach(ctx context.Context, f *framework.Framework, c clientset.Interface, pod *v1.Pod, nodeName string, volumePaths []string) { 384 ginkgo.By("Deleting pod") 385 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, pod), "Failed to delete pod ", pod.Name) 386 387 ginkgo.By("Waiting for volume to be detached from the node") 388 for _, volumePath := range volumePaths { 389 framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, volumePath, nodeName)) 390 } 391 }