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  }