csi_block.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*
  2. Copyright 2018 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. /*
  14. This file defines block volume related methods for CSI driver.
  15. CSI driver is responsible for staging/publishing volumes to their staging/publish paths.
  16. Mapping and unmapping of a device in a publish path to its global map path and its
  17. pod device map path are done by operation_executor through MapBlockVolume/UnmapBlockVolume
  18. (MapBlockVolume and UnmapBlockVolume take care for lock, symlink, and bind mount).
  19. Summary of block volume related CSI driver's methods are as follows:
  20. - GetGlobalMapPath returns a global map path,
  21. - GetPodDeviceMapPath returns a pod device map path and filename,
  22. - SetUpDevice calls CSI's NodeStageVolume and stage a volume to its staging path,
  23. - MapPodDevice calls CSI's NodePublishVolume and publish a volume to its publish path,
  24. - UnmapPodDevice calls CSI's NodeUnpublishVolume and unpublish a volume from its publish path,
  25. - TearDownDevice calls CSI's NodeUnstageVolume and unstage a volume from its staging path.
  26. These methods are called by below sequences:
  27. - operation_executor.MountVolume
  28. - csi.GetGlobalMapPath
  29. - csi.SetupDevice
  30. - NodeStageVolume
  31. - ASW.MarkDeviceAsMounted
  32. - csi.GetPodDeviceMapPath
  33. - csi.MapPodDevice
  34. - NodePublishVolume
  35. - util.MapBlockVolume
  36. - ASW.MarkVolumeAsMounted
  37. - operation_executor.UnmountVolume
  38. - csi.GetPodDeviceMapPath
  39. - util.UnmapBlockVolume
  40. - csi.UnmapPodDevice
  41. - NodeUnpublishVolume
  42. - ASW.MarkVolumeAsUnmounted
  43. - operation_executor.UnmountDevice
  44. - csi.TearDownDevice
  45. - NodeUnstageVolume
  46. - ASW.MarkDeviceAsUnmounted
  47. After successful MountVolume for block volume, directory structure will be like below:
  48. /dev/loopX ... Descriptor lock(Loopback device to mapFile under global map path)
  49. /var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/{specName}/dev/ ... Global map path
  50. /var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/{specName}/dev/{podUID} ... MapFile(Bind mount to publish Path)
  51. /var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/staging/{specName} ... Staging path
  52. /var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/publish/{specName}/{podUID} ... Publish path
  53. /var/lib/kubelet/pods/{podUID}/volumeDevices/kubernetes.io~csi/ ... Pod device map path
  54. /var/lib/kubelet/pods/{podUID}/volumeDevices/kubernetes.io~csi/{specName} ... MapFile(Symlink to publish path)
  55. */
  56. package csi
  57. import (
  58. "context"
  59. "errors"
  60. "fmt"
  61. "os"
  62. "path/filepath"
  63. v1 "k8s.io/api/core/v1"
  64. storage "k8s.io/api/storage/v1"
  65. meta "k8s.io/apimachinery/pkg/apis/meta/v1"
  66. "k8s.io/apimachinery/pkg/types"
  67. "k8s.io/client-go/kubernetes"
  68. "k8s.io/klog"
  69. "k8s.io/kubernetes/pkg/util/removeall"
  70. "k8s.io/kubernetes/pkg/volume"
  71. volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
  72. utilstrings "k8s.io/utils/strings"
  73. )
  74. type csiBlockMapper struct {
  75. csiClientGetter
  76. k8s kubernetes.Interface
  77. plugin *csiPlugin
  78. driverName csiDriverName
  79. specName string
  80. volumeID string
  81. readOnly bool
  82. spec *volume.Spec
  83. podUID types.UID
  84. volumeInfo map[string]string
  85. }
  86. var _ volume.BlockVolumeMapper = &csiBlockMapper{}
  87. var _ volume.CustomBlockVolumeMapper = &csiBlockMapper{}
  88. // GetGlobalMapPath returns a global map path (on the node) to a device file which will be symlinked to
  89. // Example: plugins/kubernetes.io/csi/volumeDevices/{specName}/dev
  90. func (m *csiBlockMapper) GetGlobalMapPath(spec *volume.Spec) (string, error) {
  91. dir := getVolumeDevicePluginDir(m.specName, m.plugin.host)
  92. klog.V(4).Infof(log("blockMapper.GetGlobalMapPath = %s", dir))
  93. return dir, nil
  94. }
  95. // getStagingPath returns a staging path for a directory (on the node) that should be used on NodeStageVolume/NodeUnstageVolume
  96. // Example: plugins/kubernetes.io/csi/volumeDevices/staging/{specName}
  97. func (m *csiBlockMapper) getStagingPath() string {
  98. return filepath.Join(m.plugin.host.GetVolumeDevicePluginDir(CSIPluginName), "staging", m.specName)
  99. }
  100. // getPublishDir returns path to a directory, where the volume is published to each pod.
  101. // Example: plugins/kubernetes.io/csi/volumeDevices/publish/{specName}
  102. func (m *csiBlockMapper) getPublishDir() string {
  103. return filepath.Join(m.plugin.host.GetVolumeDevicePluginDir(CSIPluginName), "publish", m.specName)
  104. }
  105. // getPublishPath returns a publish path for a file (on the node) that should be used on NodePublishVolume/NodeUnpublishVolume
  106. // Example: plugins/kubernetes.io/csi/volumeDevices/publish/{specName}/{podUID}
  107. func (m *csiBlockMapper) getPublishPath() string {
  108. return filepath.Join(m.getPublishDir(), string(m.podUID))
  109. }
  110. // GetPodDeviceMapPath returns pod's device file which will be mapped to a volume
  111. // returns: pods/{podUID}/volumeDevices/kubernetes.io~csi, {specName}
  112. func (m *csiBlockMapper) GetPodDeviceMapPath() (string, string) {
  113. path := m.plugin.host.GetPodVolumeDeviceDir(m.podUID, utilstrings.EscapeQualifiedName(CSIPluginName))
  114. klog.V(4).Infof(log("blockMapper.GetPodDeviceMapPath [path=%s; name=%s]", path, m.specName))
  115. return path, m.specName
  116. }
  117. // stageVolumeForBlock stages a block volume to stagingPath
  118. func (m *csiBlockMapper) stageVolumeForBlock(
  119. ctx context.Context,
  120. csi csiClient,
  121. accessMode v1.PersistentVolumeAccessMode,
  122. csiSource *v1.CSIPersistentVolumeSource,
  123. attachment *storage.VolumeAttachment,
  124. ) (string, error) {
  125. klog.V(4).Infof(log("blockMapper.stageVolumeForBlock called"))
  126. stagingPath := m.getStagingPath()
  127. klog.V(4).Infof(log("blockMapper.stageVolumeForBlock stagingPath set [%s]", stagingPath))
  128. // Check whether "STAGE_UNSTAGE_VOLUME" is set
  129. stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx)
  130. if err != nil {
  131. return "", errors.New(log("blockMapper.stageVolumeForBlock failed to check STAGE_UNSTAGE_VOLUME capability: %v", err))
  132. }
  133. if !stageUnstageSet {
  134. klog.Infof(log("blockMapper.stageVolumeForBlock STAGE_UNSTAGE_VOLUME capability not set. Skipping MountDevice..."))
  135. return "", nil
  136. }
  137. publishVolumeInfo := map[string]string{}
  138. if attachment != nil {
  139. publishVolumeInfo = attachment.Status.AttachmentMetadata
  140. }
  141. nodeStageSecrets := map[string]string{}
  142. if csiSource.NodeStageSecretRef != nil {
  143. nodeStageSecrets, err = getCredentialsFromSecret(m.k8s, csiSource.NodeStageSecretRef)
  144. if err != nil {
  145. return "", fmt.Errorf("failed to get NodeStageSecretRef %s/%s: %v",
  146. csiSource.NodeStageSecretRef.Namespace, csiSource.NodeStageSecretRef.Name, err)
  147. }
  148. }
  149. // Creating a stagingPath directory before call to NodeStageVolume
  150. if err := os.MkdirAll(stagingPath, 0750); err != nil {
  151. return "", errors.New(log("blockMapper.stageVolumeForBlock failed to create dir %s: %v", stagingPath, err))
  152. }
  153. klog.V(4).Info(log("blockMapper.stageVolumeForBlock created stagingPath directory successfully [%s]", stagingPath))
  154. // Request to stage a block volume to stagingPath.
  155. // Expected implementation for driver is creating driver specific resource on stagingPath and
  156. // attaching the block volume to the node.
  157. err = csi.NodeStageVolume(ctx,
  158. csiSource.VolumeHandle,
  159. publishVolumeInfo,
  160. stagingPath,
  161. fsTypeBlockName,
  162. accessMode,
  163. nodeStageSecrets,
  164. csiSource.VolumeAttributes,
  165. nil /* MountOptions */)
  166. if err != nil {
  167. return "", errors.New(log("blockMapper.stageVolumeForBlock failed: %v", err))
  168. }
  169. klog.V(4).Infof(log("blockMapper.stageVolumeForBlock successfully requested NodeStageVolume [%s]", stagingPath))
  170. return stagingPath, nil
  171. }
  172. // publishVolumeForBlock publishes a block volume to publishPath
  173. func (m *csiBlockMapper) publishVolumeForBlock(
  174. ctx context.Context,
  175. csi csiClient,
  176. accessMode v1.PersistentVolumeAccessMode,
  177. csiSource *v1.CSIPersistentVolumeSource,
  178. attachment *storage.VolumeAttachment,
  179. ) (string, error) {
  180. klog.V(4).Infof(log("blockMapper.publishVolumeForBlock called"))
  181. publishVolumeInfo := map[string]string{}
  182. if attachment != nil {
  183. publishVolumeInfo = attachment.Status.AttachmentMetadata
  184. }
  185. nodePublishSecrets := map[string]string{}
  186. var err error
  187. if csiSource.NodePublishSecretRef != nil {
  188. nodePublishSecrets, err = getCredentialsFromSecret(m.k8s, csiSource.NodePublishSecretRef)
  189. if err != nil {
  190. return "", errors.New(log("blockMapper.publishVolumeForBlock failed to get NodePublishSecretRef %s/%s: %v",
  191. csiSource.NodePublishSecretRef.Namespace, csiSource.NodePublishSecretRef.Name, err))
  192. }
  193. }
  194. publishPath := m.getPublishPath()
  195. // Setup a parent directory for publishPath before call to NodePublishVolume
  196. publishDir := filepath.Dir(publishPath)
  197. if err := os.MkdirAll(publishDir, 0750); err != nil {
  198. return "", errors.New(log("blockMapper.publishVolumeForBlock failed to create dir %s: %v", publishDir, err))
  199. }
  200. klog.V(4).Info(log("blockMapper.publishVolumeForBlock created directory for publishPath successfully [%s]", publishDir))
  201. // Request to publish a block volume to publishPath.
  202. // Expectation for driver is to place a block volume on the publishPath, by bind-mounting the device file on the publishPath or
  203. // creating device file on the publishPath.
  204. // Parent directory for publishPath is created by k8s, but driver is responsible for creating publishPath itself.
  205. // If driver doesn't implement NodeStageVolume, attaching the block volume to the node may be done, here.
  206. err = csi.NodePublishVolume(
  207. ctx,
  208. m.volumeID,
  209. m.readOnly,
  210. m.getStagingPath(),
  211. publishPath,
  212. accessMode,
  213. publishVolumeInfo,
  214. csiSource.VolumeAttributes,
  215. nodePublishSecrets,
  216. fsTypeBlockName,
  217. []string{},
  218. )
  219. if err != nil {
  220. return "", errors.New(log("blockMapper.publishVolumeForBlock failed: %v", err))
  221. }
  222. return publishPath, nil
  223. }
  224. // SetUpDevice ensures the device is attached returns path where the device is located.
  225. func (m *csiBlockMapper) SetUpDevice() error {
  226. if !m.plugin.blockEnabled {
  227. return errors.New("CSIBlockVolume feature not enabled")
  228. }
  229. klog.V(4).Infof(log("blockMapper.SetUpDevice called"))
  230. // Get csiSource from spec
  231. if m.spec == nil {
  232. return errors.New(log("blockMapper.SetUpDevice spec is nil"))
  233. }
  234. csiSource, err := getCSISourceFromSpec(m.spec)
  235. if err != nil {
  236. return errors.New(log("blockMapper.SetUpDevice failed to get CSI persistent source: %v", err))
  237. }
  238. driverName := csiSource.Driver
  239. skip, err := m.plugin.skipAttach(driverName)
  240. if err != nil {
  241. return errors.New(log("blockMapper.SetupDevice failed to check CSIDriver for %s: %v", driverName, err))
  242. }
  243. var attachment *storage.VolumeAttachment
  244. if !skip {
  245. // Search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName
  246. nodeName := string(m.plugin.host.GetNodeName())
  247. attachID := getAttachmentName(csiSource.VolumeHandle, csiSource.Driver, nodeName)
  248. attachment, err = m.k8s.StorageV1().VolumeAttachments().Get(context.TODO(), attachID, meta.GetOptions{})
  249. if err != nil {
  250. return errors.New(log("blockMapper.SetupDevice failed to get volume attachment [id=%v]: %v", attachID, err))
  251. }
  252. }
  253. //TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI
  254. accessMode := v1.ReadWriteOnce
  255. if m.spec.PersistentVolume.Spec.AccessModes != nil {
  256. accessMode = m.spec.PersistentVolume.Spec.AccessModes[0]
  257. }
  258. ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
  259. defer cancel()
  260. csiClient, err := m.csiClientGetter.Get()
  261. if err != nil {
  262. return errors.New(log("blockMapper.SetUpDevice failed to get CSI client: %v", err))
  263. }
  264. // Call NodeStageVolume
  265. _, err = m.stageVolumeForBlock(ctx, csiClient, accessMode, csiSource, attachment)
  266. if err != nil {
  267. if volumetypes.IsOperationFinishedError(err) {
  268. cleanupErr := m.cleanupOrphanDeviceFiles()
  269. if cleanupErr != nil {
  270. // V(4) for not so serious error
  271. klog.V(4).Infof("Failed to clean up block volume directory %s", cleanupErr)
  272. }
  273. }
  274. return err
  275. }
  276. return nil
  277. }
  278. func (m *csiBlockMapper) MapPodDevice() (string, error) {
  279. if !m.plugin.blockEnabled {
  280. return "", errors.New("CSIBlockVolume feature not enabled")
  281. }
  282. klog.V(4).Infof(log("blockMapper.MapPodDevice called"))
  283. // Get csiSource from spec
  284. if m.spec == nil {
  285. return "", errors.New(log("blockMapper.MapPodDevice spec is nil"))
  286. }
  287. csiSource, err := getCSISourceFromSpec(m.spec)
  288. if err != nil {
  289. return "", errors.New(log("blockMapper.MapPodDevice failed to get CSI persistent source: %v", err))
  290. }
  291. driverName := csiSource.Driver
  292. skip, err := m.plugin.skipAttach(driverName)
  293. if err != nil {
  294. return "", errors.New(log("blockMapper.MapPodDevice failed to check CSIDriver for %s: %v", driverName, err))
  295. }
  296. var attachment *storage.VolumeAttachment
  297. if !skip {
  298. // Search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName
  299. nodeName := string(m.plugin.host.GetNodeName())
  300. attachID := getAttachmentName(csiSource.VolumeHandle, csiSource.Driver, nodeName)
  301. attachment, err = m.k8s.StorageV1().VolumeAttachments().Get(context.TODO(), attachID, meta.GetOptions{})
  302. if err != nil {
  303. return "", errors.New(log("blockMapper.MapPodDevice failed to get volume attachment [id=%v]: %v", attachID, err))
  304. }
  305. }
  306. //TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI
  307. accessMode := v1.ReadWriteOnce
  308. if m.spec.PersistentVolume.Spec.AccessModes != nil {
  309. accessMode = m.spec.PersistentVolume.Spec.AccessModes[0]
  310. }
  311. ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
  312. defer cancel()
  313. csiClient, err := m.csiClientGetter.Get()
  314. if err != nil {
  315. return "", errors.New(log("blockMapper.MapPodDevice failed to get CSI client: %v", err))
  316. }
  317. // Call NodePublishVolume
  318. publishPath, err := m.publishVolumeForBlock(ctx, csiClient, accessMode, csiSource, attachment)
  319. if err != nil {
  320. return "", err
  321. }
  322. return publishPath, nil
  323. }
  324. var _ volume.BlockVolumeUnmapper = &csiBlockMapper{}
  325. var _ volume.CustomBlockVolumeUnmapper = &csiBlockMapper{}
  326. // unpublishVolumeForBlock unpublishes a block volume from publishPath
  327. func (m *csiBlockMapper) unpublishVolumeForBlock(ctx context.Context, csi csiClient, publishPath string) error {
  328. // Request to unpublish a block volume from publishPath.
  329. // Expectation for driver is to remove block volume from the publishPath, by unmounting bind-mounted device file
  330. // or deleting device file.
  331. // Driver is responsible for deleting publishPath itself.
  332. // If driver doesn't implement NodeUnstageVolume, detaching the block volume from the node may be done, here.
  333. if err := csi.NodeUnpublishVolume(ctx, m.volumeID, publishPath); err != nil {
  334. return errors.New(log("blockMapper.unpublishVolumeForBlock failed: %v", err))
  335. }
  336. klog.V(4).Infof(log("blockMapper.unpublishVolumeForBlock NodeUnpublished successfully [%s]", publishPath))
  337. return nil
  338. }
  339. // unstageVolumeForBlock unstages a block volume from stagingPath
  340. func (m *csiBlockMapper) unstageVolumeForBlock(ctx context.Context, csi csiClient, stagingPath string) error {
  341. // Check whether "STAGE_UNSTAGE_VOLUME" is set
  342. stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx)
  343. if err != nil {
  344. return errors.New(log("blockMapper.unstageVolumeForBlock failed to check STAGE_UNSTAGE_VOLUME capability: %v", err))
  345. }
  346. if !stageUnstageSet {
  347. klog.Infof(log("blockMapper.unstageVolumeForBlock STAGE_UNSTAGE_VOLUME capability not set. Skipping unstageVolumeForBlock ..."))
  348. return nil
  349. }
  350. // Request to unstage a block volume from stagingPath.
  351. // Expected implementation for driver is removing driver specific resource in stagingPath and
  352. // detaching the block volume from the node.
  353. if err := csi.NodeUnstageVolume(ctx, m.volumeID, stagingPath); err != nil {
  354. return errors.New(log("blockMapper.unstageVolumeForBlock failed: %v", err))
  355. }
  356. klog.V(4).Infof(log("blockMapper.unstageVolumeForBlock NodeUnstageVolume successfully [%s]", stagingPath))
  357. // Remove stagingPath directory and its contents
  358. if err := os.RemoveAll(stagingPath); err != nil {
  359. return errors.New(log("blockMapper.unstageVolumeForBlock failed to remove staging path after NodeUnstageVolume() error [%s]: %v", stagingPath, err))
  360. }
  361. return nil
  362. }
  363. // TearDownDevice removes traces of the SetUpDevice.
  364. func (m *csiBlockMapper) TearDownDevice(globalMapPath, devicePath string) error {
  365. if !m.plugin.blockEnabled {
  366. return errors.New("CSIBlockVolume feature not enabled")
  367. }
  368. ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
  369. defer cancel()
  370. csiClient, err := m.csiClientGetter.Get()
  371. if err != nil {
  372. return errors.New(log("blockMapper.TearDownDevice failed to get CSI client: %v", err))
  373. }
  374. // Call NodeUnstageVolume
  375. stagingPath := m.getStagingPath()
  376. if _, err := os.Stat(stagingPath); err != nil {
  377. if os.IsNotExist(err) {
  378. klog.V(4).Infof(log("blockMapper.TearDownDevice stagingPath(%s) has already been deleted, skip calling NodeUnstageVolume", stagingPath))
  379. } else {
  380. return err
  381. }
  382. } else {
  383. err := m.unstageVolumeForBlock(ctx, csiClient, stagingPath)
  384. if err != nil {
  385. return err
  386. }
  387. }
  388. if err = m.cleanupOrphanDeviceFiles(); err != nil {
  389. // V(4) for not so serious error
  390. klog.V(4).Infof("Failed to clean up block volume directory %s", err)
  391. }
  392. return nil
  393. }
  394. // Clean up any orphan files / directories when a block volume is being unstaged.
  395. // At this point we can be sure that there is no pod using the volume and all
  396. // files are indeed orphaned.
  397. func (m *csiBlockMapper) cleanupOrphanDeviceFiles() error {
  398. // Remove artifacts of NodePublish.
  399. // publishDir: xxx/plugins/kubernetes.io/csi/volumeDevices/publish/<volume name>
  400. // Each PublishVolume() created a subdirectory there. Since everything should be
  401. // already unpublished at this point, the directory should be empty by now.
  402. publishDir := m.getPublishDir()
  403. if err := os.Remove(publishDir); err != nil && !os.IsNotExist(err) {
  404. return errors.New(log("failed to remove publish directory [%s]: %v", publishDir, err))
  405. }
  406. // Remove artifacts of NodeStage.
  407. // stagingPath: xxx/plugins/kubernetes.io/csi/volumeDevices/staging/<volume name>
  408. stagingPath := m.getStagingPath()
  409. if err := os.Remove(stagingPath); err != nil && !os.IsNotExist(err) {
  410. return errors.New(log("failed to delete volume staging path [%s]: %v", stagingPath, err))
  411. }
  412. // Remove everything under xxx/plugins/kubernetes.io/csi/volumeDevices/<volume name>.
  413. // At this point it contains only "data/vol_data.json" and empty "dev/".
  414. volumeDir := getVolumePluginDir(m.specName, m.plugin.host)
  415. mounter := m.plugin.host.GetMounter(m.plugin.GetPluginName())
  416. if err := removeall.RemoveAllOneFilesystem(mounter, volumeDir); err != nil {
  417. return err
  418. }
  419. return nil
  420. }
  421. // UnmapPodDevice unmaps the block device path.
  422. func (m *csiBlockMapper) UnmapPodDevice() error {
  423. if !m.plugin.blockEnabled {
  424. return errors.New("CSIBlockVolume feature not enabled")
  425. }
  426. publishPath := m.getPublishPath()
  427. csiClient, err := m.csiClientGetter.Get()
  428. if err != nil {
  429. return errors.New(log("blockMapper.UnmapPodDevice failed to get CSI client: %v", err))
  430. }
  431. ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
  432. defer cancel()
  433. // Call NodeUnpublishVolume
  434. if _, err := os.Stat(publishPath); err != nil {
  435. if os.IsNotExist(err) {
  436. klog.V(4).Infof(log("blockMapper.UnmapPodDevice publishPath(%s) has already been deleted, skip calling NodeUnpublishVolume", publishPath))
  437. } else {
  438. return err
  439. }
  440. } else {
  441. err := m.unpublishVolumeForBlock(ctx, csiClient, publishPath)
  442. if err != nil {
  443. return err
  444. }
  445. }
  446. return nil
  447. }