k8s.io/kubernetes@v1.29.3/pkg/volume/csi/csi_test.go (about) 1 /* 2 Copyright 2019 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 csi 18 19 import ( 20 "fmt" 21 "math/rand" 22 "os" 23 "path/filepath" 24 "testing" 25 "time" 26 27 api "k8s.io/api/core/v1" 28 storage "k8s.io/api/storage/v1" 29 meta "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/client-go/informers" 34 fakeclient "k8s.io/client-go/kubernetes/fake" 35 utiltesting "k8s.io/client-go/util/testing" 36 "k8s.io/kubernetes/pkg/volume" 37 volumetest "k8s.io/kubernetes/pkg/volume/testing" 38 ) 39 40 // TestCSI_VolumeAll runs a close approximation of volume workflow 41 // based on operations from the volume manager/reconciler/operation executor 42 func TestCSI_VolumeAll(t *testing.T) { 43 defaultFSGroupPolicy := storage.ReadWriteOnceWithFSTypeFSGroupPolicy 44 45 tests := []struct { 46 name string 47 specName string 48 driver string 49 volName string 50 specFunc func(specName, driver, volName string) *volume.Spec 51 podFunc func() *api.Pod 52 isInline bool 53 findPluginShouldFail bool 54 driverSpec *storage.CSIDriverSpec 55 watchTimeout time.Duration 56 }{ 57 { 58 name: "PersistentVolume", 59 specName: "pv2", 60 driver: "simple-driver", 61 volName: "vol2", 62 specFunc: func(specName, driver, volName string) *volume.Spec { 63 return volume.NewSpecFromPersistentVolume(makeTestPV(specName, 20, driver, volName), false) 64 }, 65 podFunc: func() *api.Pod { 66 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 67 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 68 }, 69 }, 70 { 71 name: "PersistentVolume with driver info", 72 specName: "pv2", 73 driver: "simple-driver", 74 volName: "vol2", 75 specFunc: func(specName, driver, volName string) *volume.Spec { 76 return volume.NewSpecFromPersistentVolume(makeTestPV(specName, 20, driver, volName), false) 77 }, 78 podFunc: func() *api.Pod { 79 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 80 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 81 }, 82 driverSpec: &storage.CSIDriverSpec{ 83 // Required for the driver to be accepted for the persistent volume. 84 VolumeLifecycleModes: []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent}, 85 FSGroupPolicy: &defaultFSGroupPolicy, 86 }, 87 }, 88 { 89 name: "PersistentVolume with wrong mode in driver info", 90 specName: "pv2", 91 driver: "simple-driver", 92 volName: "vol2", 93 specFunc: func(specName, driver, volName string) *volume.Spec { 94 return volume.NewSpecFromPersistentVolume(makeTestPV(specName, 20, driver, volName), false) 95 }, 96 podFunc: func() *api.Pod { 97 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 98 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 99 }, 100 driverSpec: &storage.CSIDriverSpec{ 101 // This will cause the volume to be rejected. 102 VolumeLifecycleModes: []storage.VolumeLifecycleMode{storage.VolumeLifecycleEphemeral}, 103 FSGroupPolicy: &defaultFSGroupPolicy, 104 }, 105 }, 106 { 107 name: "ephemeral inline supported", 108 driver: "inline-driver-1", 109 volName: "test.vol2", 110 specFunc: func(specName, driver, volName string) *volume.Spec { 111 return volume.NewSpecFromVolume(makeTestVol(specName, driver)) 112 }, 113 podFunc: func() *api.Pod { 114 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 115 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 116 }, 117 isInline: true, 118 driverSpec: &storage.CSIDriverSpec{ 119 // Required for the driver to be accepted for the inline volume. 120 VolumeLifecycleModes: []storage.VolumeLifecycleMode{storage.VolumeLifecycleEphemeral}, 121 FSGroupPolicy: &defaultFSGroupPolicy, 122 }, 123 }, 124 { 125 name: "ephemeral inline also supported", 126 driver: "inline-driver-1", 127 volName: "test.vol2", 128 specFunc: func(specName, driver, volName string) *volume.Spec { 129 return volume.NewSpecFromVolume(makeTestVol(specName, driver)) 130 }, 131 podFunc: func() *api.Pod { 132 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 133 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 134 }, 135 isInline: true, 136 driverSpec: &storage.CSIDriverSpec{ 137 // Required for the driver to be accepted for the inline volume. 138 VolumeLifecycleModes: []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent, storage.VolumeLifecycleEphemeral}, 139 FSGroupPolicy: &defaultFSGroupPolicy, 140 }, 141 }, 142 { 143 name: "ephemeral inline without CSIDriver info", 144 driver: "inline-driver-2", 145 volName: "test.vol3", 146 specFunc: func(specName, driver, volName string) *volume.Spec { 147 return volume.NewSpecFromVolume(makeTestVol(specName, driver)) 148 }, 149 podFunc: func() *api.Pod { 150 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 151 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 152 }, 153 isInline: true, 154 }, 155 { 156 name: "ephemeral inline with driver that has no mode", 157 driver: "inline-driver-3", 158 volName: "test.vol4", 159 specFunc: func(specName, driver, volName string) *volume.Spec { 160 return volume.NewSpecFromVolume(makeTestVol(specName, driver)) 161 }, 162 podFunc: func() *api.Pod { 163 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 164 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 165 }, 166 isInline: true, 167 driverSpec: &storage.CSIDriverSpec{ 168 // This means the driver *cannot* handle the inline volume because 169 // the default is "persistent". 170 VolumeLifecycleModes: nil, 171 FSGroupPolicy: &defaultFSGroupPolicy, 172 }, 173 }, 174 { 175 name: "ephemeral inline with driver that has wrong mode", 176 driver: "inline-driver-3", 177 volName: "test.vol4", 178 specFunc: func(specName, driver, volName string) *volume.Spec { 179 return volume.NewSpecFromVolume(makeTestVol(specName, driver)) 180 }, 181 podFunc: func() *api.Pod { 182 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 183 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 184 }, 185 isInline: true, 186 driverSpec: &storage.CSIDriverSpec{ 187 // This means the driver *cannot* handle the inline volume. 188 VolumeLifecycleModes: []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent}, 189 FSGroupPolicy: &defaultFSGroupPolicy, 190 }, 191 }, 192 { 193 name: "missing spec", 194 specName: "pv2", 195 driver: "simple-driver", 196 volName: "vol2", 197 specFunc: func(specName, driver, volName string) *volume.Spec { 198 return nil 199 }, 200 podFunc: func() *api.Pod { 201 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 202 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 203 }, 204 findPluginShouldFail: true, 205 }, 206 { 207 name: "incomplete spec", 208 specName: "pv2", 209 driver: "simple-driver", 210 volName: "vol2", 211 specFunc: func(specName, driver, volName string) *volume.Spec { 212 return &volume.Spec{ReadOnly: true} 213 }, 214 podFunc: func() *api.Pod { 215 podUID := types.UID(fmt.Sprintf("%08X", rand.Uint64())) 216 return &api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}} 217 }, 218 findPluginShouldFail: true, 219 }, 220 } 221 222 for _, test := range tests { 223 t.Run(test.name, func(t *testing.T) { 224 tmpDir, err := utiltesting.MkTmpdir("csi-test") 225 if err != nil { 226 t.Fatalf("can't create temp dir: %v", err) 227 } 228 defer os.RemoveAll(tmpDir) 229 230 var driverInfo *storage.CSIDriver 231 objs := []runtime.Object{} 232 if test.driverSpec != nil { 233 driverInfo = &storage.CSIDriver{ 234 ObjectMeta: meta.ObjectMeta{ 235 Name: test.driver, 236 }, 237 Spec: *test.driverSpec, 238 } 239 objs = append(objs, driverInfo) 240 } 241 objs = append(objs, &api.Node{ 242 ObjectMeta: meta.ObjectMeta{ 243 Name: "fakeNode", 244 }, 245 Spec: api.NodeSpec{}, 246 }) 247 248 client := fakeclient.NewSimpleClientset(objs...) 249 250 factory := informers.NewSharedInformerFactory(client, time.Hour /* disable resync */) 251 csiDriverInformer := factory.Storage().V1().CSIDrivers() 252 volumeAttachmentInformer := factory.Storage().V1().VolumeAttachments() 253 if driverInfo != nil { 254 csiDriverInformer.Informer().GetStore().Add(driverInfo) 255 } 256 257 factory.Start(wait.NeverStop) 258 factory.WaitForCacheSync(wait.NeverStop) 259 260 attachDetachVolumeHost := volumetest.NewFakeAttachDetachVolumeHostWithCSINodeName(t, 261 tmpDir, 262 client, 263 ProbeVolumePlugins(), 264 "fakeNode", 265 csiDriverInformer.Lister(), 266 volumeAttachmentInformer.Lister(), 267 ) 268 attachDetachPlugMgr := attachDetachVolumeHost.GetPluginMgr() 269 csiClient := setupClient(t, true) 270 271 volSpec := test.specFunc(test.specName, test.driver, test.volName) 272 pod := test.podFunc() 273 attachName := getAttachmentName(test.volName, test.driver, string(attachDetachVolumeHost.GetNodeName())) 274 t.Log("csiTest.VolumeAll starting...") 275 276 // *************** Attach/Mount volume resources ****************// 277 // attach volume 278 t.Log("csiTest.VolumeAll Attaching volume...") 279 attachPlug, err := attachDetachPlugMgr.FindAttachablePluginBySpec(volSpec) 280 if err != nil { 281 if !test.findPluginShouldFail { 282 t.Fatalf("csiTest.VolumeAll PluginManager.FindAttachablePluginBySpec failed: %v", err) 283 } else { 284 t.Log("csiTest.VolumeAll failed: ", err) 285 return 286 } 287 } 288 289 if test.isInline && attachPlug != nil { 290 t.Fatal("csiTest.VolumeAll AttachablePlugin found with ephemeral volume") 291 } 292 if !test.isInline && attachPlug == nil { 293 t.Fatal("csiTest.VolumeAll AttachablePlugin not found with PV") 294 } 295 296 var devicePath string 297 if attachPlug != nil { 298 t.Log("csiTest.VolumeAll attacher.Attach starting") 299 300 var volAttacher volume.Attacher 301 302 volAttacher, err := attachPlug.NewAttacher() 303 if err != nil { 304 t.Fatal("csiTest.VolumeAll failed to create new attacher: ", err) 305 } 306 307 // creates VolumeAttachment and blocks until it is marked attached (done by external attacher) 308 go func() { 309 attachID, err := volAttacher.Attach(volSpec, attachDetachVolumeHost.GetNodeName()) 310 if err != nil { 311 t.Errorf("csiTest.VolumeAll attacher.Attach failed: %s", err) 312 return 313 } 314 t.Logf("csiTest.VolumeAll got attachID %s", attachID) 315 }() 316 317 // Simulates external-attacher and marks VolumeAttachment.Status.Attached = true 318 markVolumeAttached(t, attachDetachVolumeHost.GetKubeClient(), nil, attachName, storage.VolumeAttachmentStatus{Attached: true}) 319 320 // Observe attach on this node. 321 devicePath, err = volAttacher.WaitForAttach(volSpec, "", pod, 500*time.Millisecond) 322 if err != nil { 323 t.Fatal("csiTest.VolumeAll attacher.WaitForAttach failed:", err) 324 } 325 326 if devicePath != attachName { 327 t.Fatalf("csiTest.VolumeAll attacher.WaitForAttach got unexpected value %s", devicePath) 328 } 329 330 t.Log("csiTest.VolumeAll attacher.WaitForAttach succeeded OK, attachment ID:", devicePath) 331 332 } else { 333 t.Log("csiTest.VolumeAll volume attacher not found, skipping attachment") 334 } 335 336 // The reason for separate volume hosts here is because the attach/detach behavior is exclusive to the 337 // CSI plugin running in the AttachDetachController. Similarly, the mount/unmount behavior is exclusive 338 // to the CSI plugin running in the Kubelet. 339 kubeletVolumeHost := volumetest.NewFakeKubeletVolumeHostWithCSINodeName(t, 340 tmpDir, 341 client, 342 ProbeVolumePlugins(), 343 "fakeNode", 344 csiDriverInformer.Lister(), 345 volumeAttachmentInformer.Lister(), 346 ) 347 kubeletPlugMgr := kubeletVolumeHost.GetPluginMgr() 348 349 // Mount Device 350 t.Log("csiTest.VolumeAll Mouting device...") 351 devicePlug, err := kubeletPlugMgr.FindDeviceMountablePluginBySpec(volSpec) 352 if err != nil { 353 t.Fatalf("csiTest.VolumeAll PluginManager.FindDeviceMountablePluginBySpec failed: %v", err) 354 } 355 356 if test.isInline && devicePlug != nil { 357 t.Fatal("csiTest.VolumeAll DeviceMountablePlugin found with ephemeral volume") 358 } 359 if !test.isInline && devicePlug == nil { 360 t.Fatal("csiTest.VolumeAll DeviceMountablePlugin not found with PV") 361 } 362 363 var devMounter volume.DeviceMounter 364 if devicePlug != nil { 365 devMounter, err = devicePlug.NewDeviceMounter() 366 if err != nil { 367 t.Fatal("csiTest.VolumeAll failed to create new device mounter: ", err) 368 } 369 } 370 371 if devMounter != nil { 372 csiDevMounter := getCsiAttacherFromDeviceMounter(devMounter, test.watchTimeout) 373 csiDevMounter.csiClient = csiClient 374 devMountPath, err := csiDevMounter.GetDeviceMountPath(volSpec) 375 if err != nil { 376 t.Fatalf("csiTest.VolumeAll deviceMounter.GetdeviceMountPath failed %s", err) 377 } 378 if err := csiDevMounter.MountDevice(volSpec, devicePath, devMountPath, volume.DeviceMounterArgs{}); err != nil { 379 t.Fatalf("csiTest.VolumeAll deviceMounter.MountDevice failed: %v", err) 380 } 381 t.Log("csiTest.VolumeAll device mounted at path:", devMountPath) 382 } else { 383 t.Log("csiTest.VolumeAll DeviceMountablePlugin not found, skipping deviceMounter.MountDevice") 384 } 385 386 // mount volume 387 t.Log("csiTest.VolumeAll Mouting volume...") 388 volPlug, err := kubeletPlugMgr.FindPluginBySpec(volSpec) 389 if err != nil || volPlug == nil { 390 t.Fatalf("csiTest.VolumeAll PluginMgr.FindPluginBySpec failed: %v", err) 391 } 392 393 if volPlug == nil { 394 t.Fatalf("csiTest.VolumeAll volumePlugin is nil") 395 } 396 397 if !volPlug.CanSupport(volSpec) { 398 t.Fatal("csiTest.VolumeAll volumePlugin.CanSupport returned false") 399 } 400 401 mounter, err := volPlug.NewMounter(volSpec, pod, volume.VolumeOptions{}) 402 if err != nil || mounter == nil { 403 t.Fatalf("csiTest.VolumeAll volPlugin.NewMounter is nil or error: %s", err) 404 } 405 406 var fsGroup *int64 407 if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.FSGroup != nil { 408 fsGroup = pod.Spec.SecurityContext.FSGroup 409 } 410 411 csiMounter := mounter.(*csiMountMgr) 412 csiMounter.csiClient = csiClient 413 var mounterArgs volume.MounterArgs 414 mounterArgs.FsGroup = fsGroup 415 err = csiMounter.SetUp(mounterArgs) 416 if test.isInline && (test.driverSpec == nil || !containsVolumeMode(test.driverSpec.VolumeLifecycleModes, storage.VolumeLifecycleEphemeral)) { 417 // This *must* fail because a CSIDriver.Spec.VolumeLifecycleModes entry "ephemeral" 418 // is required. 419 if err == nil { 420 t.Fatalf("csiTest.VolumeAll volPlugin.NewMounter should have failed for inline volume due to lack of support for inline volumes, got: %+v, %s", mounter, err) 421 } 422 return 423 } 424 if !test.isInline && test.driverSpec != nil && !containsVolumeMode(test.driverSpec.VolumeLifecycleModes, storage.VolumeLifecyclePersistent) { 425 // This *must* fail because a CSIDriver.Spec.VolumeLifecycleModes entry "persistent" 426 // is required when a driver object is available. 427 if err == nil { 428 t.Fatalf("csiTest.VolumeAll volPlugin.NewMounter should have failed for persistent volume due to lack of support for persistent volumes, got: %+v, %s", mounter, err) 429 } 430 return 431 } 432 if err != nil { 433 t.Fatalf("csiTest.VolumeAll mounter.Setup(fsGroup) failed: %s", err) 434 } 435 t.Log("csiTest.VolumeAll mounter.Setup(fsGroup) done OK") 436 437 dataFile := filepath.Join(filepath.Dir(mounter.GetPath()), volDataFileName) 438 if _, err := os.Stat(dataFile); err != nil { 439 t.Fatalf("csiTest.VolumeAll metadata JSON file not found: %s", dataFile) 440 } 441 t.Log("csiTest.VolumeAll JSON datafile generated OK:", dataFile) 442 443 // ******** Volume Reconstruction ************* // 444 volPath := filepath.Dir(csiMounter.GetPath()) 445 t.Log("csiTest.VolumeAll entering plugin.ConstructVolumeSpec for path", volPath) 446 rec, err := volPlug.ConstructVolumeSpec(test.volName, volPath) 447 if err != nil { 448 t.Fatalf("csiTest.VolumeAll plugin.ConstructVolumeSpec failed: %s", err) 449 } else { 450 if rec.Spec == nil { 451 t.Fatalf("csiTest.VolumeAll plugin.ConstructVolumeSpec returned nil spec") 452 } else { 453 volSpec = rec.Spec 454 455 if test.isInline { 456 if volSpec.Volume == nil || volSpec.Volume.CSI == nil { 457 t.Fatal("csiTest.VolumeAll reconstruction of ephemeral volumeSpec missing CSI Volume source") 458 } 459 if volSpec.Volume.CSI.Driver == "" { 460 t.Fatal("csiTest.VolumeAll reconstruction ephemral volume missing driver name") 461 } 462 } else { 463 if volSpec.PersistentVolume == nil || volSpec.PersistentVolume.Spec.CSI == nil { 464 t.Fatal("csiTest.VolumeAll reconstruction of volumeSpec missing CSI PersistentVolume source") 465 } 466 csi := volSpec.PersistentVolume.Spec.CSI 467 if csi.Driver == "" { 468 t.Fatal("csiTest.VolumeAll reconstruction of PV missing driver name") 469 } 470 if csi.VolumeHandle == "" { 471 t.Fatal("csiTest.VolumeAll reconstruction of PV missing volume handle") 472 } 473 } 474 } 475 } 476 477 // ************* Teardown everything **************** // 478 t.Log("csiTest.VolumeAll Tearing down...") 479 // unmount volume 480 t.Log("csiTest.VolumeAll Unmouting volume...") 481 volPlug, err = kubeletPlugMgr.FindPluginBySpec(volSpec) 482 if err != nil || volPlug == nil { 483 t.Fatalf("csiTest.VolumeAll PluginMgr.FindPluginBySpec failed: %v", err) 484 } 485 if volPlug == nil { 486 t.Fatalf("csiTest.VolumeAll volumePlugin is nil") 487 } 488 mounter, err = volPlug.NewMounter(volSpec, pod, volume.VolumeOptions{}) 489 if err != nil || mounter == nil { 490 t.Fatalf("csiTest.VolumeAll volPlugin.NewMounter is nil or error: %s", err) 491 } 492 493 unmounter, err := volPlug.NewUnmounter(test.specName, pod.GetUID()) 494 if err != nil { 495 t.Fatal("csiTest.VolumeAll volumePlugin.NewUnmounter failed:", err) 496 } 497 csiUnmounter := unmounter.(*csiMountMgr) 498 csiUnmounter.csiClient = csiClient 499 500 if err := csiUnmounter.TearDownAt(mounter.GetPath()); err != nil { 501 t.Fatal("csiTest.VolumeAll unmounter.TearDownAt failed:", err) 502 } 503 t.Log("csiTest.VolumeAll unmounter.TearDownAt done OK for dir:", mounter.GetPath()) 504 505 // unmount device 506 t.Log("csiTest.VolumeAll Unmouting device...") 507 devicePlug, err = kubeletPlugMgr.FindDeviceMountablePluginBySpec(volSpec) 508 if err != nil { 509 t.Fatalf("csiTest.VolumeAll failed to create mountable device plugin: %s", err) 510 } 511 512 if test.isInline && devicePlug != nil { 513 t.Fatal("csiTest.VolumeAll DeviceMountablePlugin found with ephemeral volume") 514 } 515 if !test.isInline && devicePlug == nil { 516 t.Fatal("csiTest.VolumeAll DeviceMountablePlugin not found with PV") 517 } 518 519 var devUnmounter volume.DeviceUnmounter 520 if devicePlug != nil { 521 t.Log("csiTest.VolumeAll found DeviceMountablePlugin, entering device unmouting ...") 522 devMounter, err = devicePlug.NewDeviceMounter() 523 if err != nil { 524 t.Fatal("csiTest.VolumeAll failed to create new device mounter: ", err) 525 } 526 devUnmounter, err = devicePlug.NewDeviceUnmounter() 527 if err != nil { 528 t.Fatal("csiTest.VolumeAll failed to create new device unmounter: ", err) 529 } 530 531 if devMounter != nil && devUnmounter != nil { 532 csiDevMounter := getCsiAttacherFromDeviceMounter(devMounter, test.watchTimeout) 533 csiDevUnmounter := getCsiAttacherFromDeviceUnmounter(devUnmounter, test.watchTimeout) 534 csiDevUnmounter.csiClient = csiClient 535 536 devMountPath, err := csiDevMounter.GetDeviceMountPath(volSpec) 537 if err != nil { 538 t.Fatalf("csiTest.VolumeAll deviceMounter.GetdeviceMountPath failed %s", err) 539 } 540 if err := csiDevUnmounter.UnmountDevice(devMountPath); err != nil { 541 t.Fatalf("csiTest.VolumeAll deviceMounter.UnmountDevice failed: %s", err) 542 } 543 t.Log("csiTest.VolumeAll deviceUnmounter.UnmountDevice done OK for path", devMountPath) 544 } 545 } else { 546 t.Log("csiTest.VolumeAll DeviceMountablePluginBySpec did not find a plugin, skipping unmounting.") 547 } 548 549 // detach volume 550 t.Log("csiTest.VolumeAll Detaching volume...") 551 attachPlug, err = attachDetachPlugMgr.FindAttachablePluginBySpec(volSpec) 552 if err != nil { 553 t.Fatalf("csiTest.VolumeAll PluginManager.FindAttachablePluginBySpec failed: %v", err) 554 } 555 556 if test.isInline && attachPlug != nil { 557 t.Fatal("csiTest.VolumeAll AttachablePlugin found with ephemeral volume") 558 } 559 if !test.isInline && attachPlug == nil { 560 t.Fatal("csiTest.VolumeAll AttachablePlugin not found with PV") 561 } 562 563 if attachPlug != nil { 564 volDetacher, err := attachPlug.NewDetacher() 565 if err != nil { 566 t.Fatal("csiTest.VolumeAll failed to create new detacher: ", err) 567 } 568 569 t.Log("csiTest.VolumeAll preparing detacher.Detach...") 570 volName, err := volPlug.GetVolumeName(volSpec) 571 if err != nil { 572 t.Fatal("csiTest.VolumeAll volumePlugin.GetVolumeName failed:", err) 573 } 574 csiDetacher := getCsiAttacherFromVolumeDetacher(volDetacher, test.watchTimeout) 575 csiDetacher.csiClient = csiClient 576 if err := csiDetacher.Detach(volName, attachDetachVolumeHost.GetNodeName()); err != nil { 577 t.Fatal("csiTest.VolumeAll detacher.Detach failed:", err) 578 } 579 t.Log("csiTest.VolumeAll detacher.Detach succeeded for volume", volName) 580 581 } else { 582 t.Log("csiTest.VolumeAll attachable plugin not found for plugin.Detach call, skipping") 583 } 584 }) 585 } 586 }