2015-08-11 15:19:29 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors.
|
2015-08-11 15:19:29 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package fc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2017-08-31 00:11:33 +00:00
|
|
|
"k8s.io/api/core/v1"
|
|
|
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
|
|
"k8s.io/kubernetes/pkg/features"
|
2015-08-11 15:19:29 +00:00
|
|
|
"k8s.io/kubernetes/pkg/volume"
|
2017-12-13 16:31:38 +00:00
|
|
|
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
2018-02-06 08:38:41 +00:00
|
|
|
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
2015-08-11 15:19:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type ioHandler interface {
|
|
|
|
ReadDir(dirname string) ([]os.FileInfo, error)
|
|
|
|
Lstat(name string) (os.FileInfo, error)
|
|
|
|
EvalSymlinks(path string) (string, error)
|
|
|
|
WriteFile(filename string, data []byte, perm os.FileMode) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type osIOHandler struct{}
|
|
|
|
|
2017-12-13 16:31:38 +00:00
|
|
|
const (
|
|
|
|
byPath = "/dev/disk/by-path/"
|
|
|
|
byID = "/dev/disk/by-id/"
|
|
|
|
)
|
|
|
|
|
2015-08-11 15:19:29 +00:00
|
|
|
func (handler *osIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
|
|
|
|
return ioutil.ReadDir(dirname)
|
|
|
|
}
|
|
|
|
func (handler *osIOHandler) Lstat(name string) (os.FileInfo, error) {
|
|
|
|
return os.Lstat(name)
|
|
|
|
}
|
|
|
|
func (handler *osIOHandler) EvalSymlinks(path string) (string, error) {
|
|
|
|
return filepath.EvalSymlinks(path)
|
|
|
|
}
|
|
|
|
func (handler *osIOHandler) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
|
|
|
return ioutil.WriteFile(filename, data, perm)
|
|
|
|
}
|
|
|
|
|
|
|
|
// given a wwn and lun, find the device and associated devicemapper parent
|
2017-12-13 16:31:38 +00:00
|
|
|
func findDisk(wwn, lun string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
|
2015-08-11 15:19:29 +00:00
|
|
|
fc_path := "-fc-0x" + wwn + "-lun-" + lun
|
2017-12-13 16:31:38 +00:00
|
|
|
dev_path := byPath
|
2015-08-11 15:19:29 +00:00
|
|
|
if dirs, err := io.ReadDir(dev_path); err == nil {
|
|
|
|
for _, f := range dirs {
|
|
|
|
name := f.Name()
|
|
|
|
if strings.Contains(name, fc_path) {
|
|
|
|
if disk, err1 := io.EvalSymlinks(dev_path + name); err1 == nil {
|
2017-12-13 16:31:38 +00:00
|
|
|
dm := deviceUtil.FindMultipathDeviceForDevice(disk)
|
|
|
|
glog.Infof("fc: find disk: %v, dm: %v", disk, dm)
|
2015-08-11 15:19:29 +00:00
|
|
|
return disk, dm
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", ""
|
|
|
|
}
|
|
|
|
|
2017-07-10 23:51:24 +00:00
|
|
|
// given a wwid, find the device and associated devicemapper parent
|
2017-12-13 16:31:38 +00:00
|
|
|
func findDiskWWIDs(wwid string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
|
2017-07-10 23:51:24 +00:00
|
|
|
// Example wwid format:
|
|
|
|
// 3600508b400105e210000900000490000
|
|
|
|
// <VENDOR NAME> <IDENTIFIER NUMBER>
|
|
|
|
// Example of symlink under by-id:
|
|
|
|
// /dev/by-id/scsi-3600508b400105e210000900000490000
|
|
|
|
// /dev/by-id/scsi-<VENDOR NAME>_<IDENTIFIER NUMBER>
|
|
|
|
// The wwid could contain white space and it will be replaced
|
|
|
|
// underscore when wwid is exposed under /dev/by-id.
|
|
|
|
|
|
|
|
fc_path := "scsi-" + wwid
|
2017-12-13 16:31:38 +00:00
|
|
|
dev_id := byID
|
2017-07-10 23:51:24 +00:00
|
|
|
if dirs, err := io.ReadDir(dev_id); err == nil {
|
|
|
|
for _, f := range dirs {
|
|
|
|
name := f.Name()
|
|
|
|
if name == fc_path {
|
|
|
|
disk, err := io.EvalSymlinks(dev_id + name)
|
|
|
|
if err != nil {
|
|
|
|
glog.V(2).Infof("fc: failed to find a corresponding disk from symlink[%s], error %v", dev_id+name, err)
|
|
|
|
return "", ""
|
|
|
|
}
|
2017-12-13 16:31:38 +00:00
|
|
|
dm := deviceUtil.FindMultipathDeviceForDevice(disk)
|
|
|
|
glog.Infof("fc: find disk: %v, dm: %v", disk, dm)
|
2017-07-10 23:51:24 +00:00
|
|
|
return disk, dm
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
glog.V(2).Infof("fc: failed to find a disk [%s]", dev_id+fc_path)
|
|
|
|
return "", ""
|
|
|
|
}
|
|
|
|
|
2017-07-21 20:45:44 +00:00
|
|
|
// Removes a scsi device based upon /dev/sdX name
|
|
|
|
func removeFromScsiSubsystem(deviceName string, io ioHandler) {
|
|
|
|
fileName := "/sys/block/" + deviceName + "/device/delete"
|
|
|
|
glog.V(4).Infof("fc: remove device from scsi-subsystem: path: %s", fileName)
|
|
|
|
data := []byte("1")
|
|
|
|
io.WriteFile(fileName, data, 0666)
|
|
|
|
}
|
|
|
|
|
2015-08-11 15:19:29 +00:00
|
|
|
// rescan scsi bus
|
|
|
|
func scsiHostRescan(io ioHandler) {
|
|
|
|
scsi_path := "/sys/class/scsi_host/"
|
|
|
|
if dirs, err := io.ReadDir(scsi_path); err == nil {
|
|
|
|
for _, f := range dirs {
|
|
|
|
name := scsi_path + f.Name() + "/scan"
|
|
|
|
data := []byte("- - -")
|
|
|
|
io.WriteFile(name, data, 0666)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-31 00:11:33 +00:00
|
|
|
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/target-lun-0
|
2017-07-10 23:51:24 +00:00
|
|
|
func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
|
|
|
|
if len(wwns) != 0 {
|
|
|
|
return path.Join(host.GetPluginDir(fcPluginName), wwns[0]+"-lun-"+lun)
|
|
|
|
} else {
|
|
|
|
return path.Join(host.GetPluginDir(fcPluginName), wwids[0])
|
|
|
|
}
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
|
|
|
|
2017-08-31 00:11:33 +00:00
|
|
|
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/volumeDevices/target-lun-0
|
|
|
|
func makeVDPDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
|
|
|
|
if len(wwns) != 0 {
|
|
|
|
return path.Join(host.GetVolumeDevicePluginDir(fcPluginName), wwns[0]+"-lun-"+lun)
|
|
|
|
} else {
|
|
|
|
return path.Join(host.GetVolumeDevicePluginDir(fcPluginName), wwids[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-11 15:19:29 +00:00
|
|
|
type FCUtil struct{}
|
|
|
|
|
|
|
|
func (util *FCUtil) MakeGlobalPDName(fc fcDisk) string {
|
2017-07-10 23:51:24 +00:00
|
|
|
return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
|
|
|
|
2017-08-31 00:11:33 +00:00
|
|
|
// Global volume device plugin dir
|
|
|
|
func (util *FCUtil) MakeGlobalVDPDName(fc fcDisk) string {
|
|
|
|
return makeVDPDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
|
|
|
|
}
|
|
|
|
|
|
|
|
func searchDisk(b fcDiskMounter) (string, error) {
|
2017-07-10 23:51:24 +00:00
|
|
|
var diskIds []string
|
|
|
|
var disk string
|
|
|
|
var dm string
|
|
|
|
io := b.io
|
|
|
|
wwids := b.wwids
|
|
|
|
wwns := b.wwns
|
|
|
|
lun := b.lun
|
|
|
|
|
|
|
|
if len(wwns) != 0 {
|
|
|
|
diskIds = wwns
|
|
|
|
} else {
|
|
|
|
diskIds = wwids
|
|
|
|
}
|
2015-08-11 15:19:29 +00:00
|
|
|
|
|
|
|
rescaned := false
|
|
|
|
// two-phase search:
|
|
|
|
// first phase, search existing device path, if a multipath dm is found, exit loop
|
|
|
|
// otherwise, in second phase, rescan scsi bus and search again, return with any findings
|
|
|
|
for true {
|
2017-07-10 23:51:24 +00:00
|
|
|
for _, diskId := range diskIds {
|
|
|
|
if len(wwns) != 0 {
|
2017-12-13 16:31:38 +00:00
|
|
|
disk, dm = findDisk(diskId, lun, io, b.deviceUtil)
|
2017-07-10 23:51:24 +00:00
|
|
|
} else {
|
2017-12-13 16:31:38 +00:00
|
|
|
disk, dm = findDiskWWIDs(diskId, io, b.deviceUtil)
|
2017-07-10 23:51:24 +00:00
|
|
|
}
|
2015-08-11 15:19:29 +00:00
|
|
|
// if multipath device is found, break
|
|
|
|
if dm != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if a dm is found, exit loop
|
|
|
|
if rescaned || dm != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// rescan and search again
|
|
|
|
// rescan scsi bus
|
|
|
|
scsiHostRescan(io)
|
|
|
|
rescaned = true
|
|
|
|
}
|
|
|
|
// if no disk matches input wwn and lun, exit
|
|
|
|
if disk == "" && dm == "" {
|
2017-06-21 20:20:14 +00:00
|
|
|
return "", fmt.Errorf("no fc disk found")
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if multipath devicemapper device is found, use it; otherwise use raw disk
|
|
|
|
if dm != "" {
|
2017-08-31 00:11:33 +00:00
|
|
|
return dm, nil
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
2017-08-31 00:11:33 +00:00
|
|
|
return disk, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) {
|
|
|
|
devicePath, err := searchDisk(b)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
// TODO: remove feature gate check after no longer needed
|
|
|
|
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
|
|
|
|
// If the volumeMode is 'Block', plugin don't have to format the volume.
|
|
|
|
// The globalPDPath will be created by operationexecutor. Just return devicePath here.
|
|
|
|
glog.V(5).Infof("fc: AttachDisk volumeMode: %s, devicePath: %s", b.volumeMode, devicePath)
|
|
|
|
if b.volumeMode == v1.PersistentVolumeBlock {
|
|
|
|
return devicePath, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-11 15:19:29 +00:00
|
|
|
// mount it
|
2017-06-21 20:20:14 +00:00
|
|
|
globalPDPath := util.MakeGlobalPDName(*b.fcDisk)
|
2017-09-18 21:12:20 +00:00
|
|
|
if err := os.MkdirAll(globalPDPath, 0750); err != nil {
|
|
|
|
return devicePath, fmt.Errorf("fc: failed to mkdir %s, error", globalPDPath)
|
|
|
|
}
|
|
|
|
|
2015-08-11 15:19:29 +00:00
|
|
|
noMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
|
2017-08-26 04:34:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return devicePath, fmt.Errorf("Heuristic determination of mount point failed:%v", err)
|
|
|
|
}
|
2015-08-11 15:19:29 +00:00
|
|
|
if !noMnt {
|
|
|
|
glog.Infof("fc: %s already mounted", globalPDPath)
|
2017-06-21 20:20:14 +00:00
|
|
|
return devicePath, nil
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
|
|
|
|
2015-11-05 21:49:40 +00:00
|
|
|
err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, nil)
|
2015-08-11 15:19:29 +00:00
|
|
|
if err != nil {
|
2017-06-21 20:20:14 +00:00
|
|
|
return devicePath, fmt.Errorf("fc: failed to mount fc volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err)
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
|
|
|
|
2017-06-21 20:20:14 +00:00
|
|
|
return devicePath, err
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
|
|
|
|
2017-12-13 16:31:38 +00:00
|
|
|
// DetachDisk removes scsi device file such as /dev/sdX from the node.
|
|
|
|
func (util *FCUtil) DetachDisk(c fcDiskUnmounter, devicePath string) error {
|
|
|
|
var devices []string
|
|
|
|
// devicePath might be like /dev/mapper/mpathX. Find destination.
|
|
|
|
dstPath, err := c.io.EvalSymlinks(devicePath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Find slave
|
|
|
|
if strings.HasPrefix(dstPath, "/dev/dm-") {
|
|
|
|
devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dstPath)
|
|
|
|
} else {
|
|
|
|
// Add single devicepath to devices
|
|
|
|
devices = append(devices, dstPath)
|
|
|
|
}
|
|
|
|
glog.V(4).Infof("fc: DetachDisk devicePath: %v, dstPath: %v, devices: %v", devicePath, dstPath, devices)
|
|
|
|
var lastErr error
|
|
|
|
for _, device := range devices {
|
|
|
|
err := util.detachFCDisk(c.io, device)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
|
|
|
|
lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if lastErr != nil {
|
|
|
|
glog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
|
|
|
|
return lastErr
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// detachFCDisk removes scsi device file such as /dev/sdX from the node.
|
|
|
|
func (util *FCUtil) detachFCDisk(io ioHandler, devicePath string) error {
|
2017-07-21 20:45:44 +00:00
|
|
|
// Remove scsi device from the node.
|
2017-12-13 16:31:38 +00:00
|
|
|
if !strings.HasPrefix(devicePath, "/dev/") {
|
|
|
|
return fmt.Errorf("fc detach disk: invalid device name: %s", devicePath)
|
2015-08-11 15:19:29 +00:00
|
|
|
}
|
2017-12-13 16:31:38 +00:00
|
|
|
arr := strings.Split(devicePath, "/")
|
2017-07-21 20:45:44 +00:00
|
|
|
dev := arr[len(arr)-1]
|
2017-12-13 16:31:38 +00:00
|
|
|
removeFromScsiSubsystem(dev, io)
|
2015-08-11 15:19:29 +00:00
|
|
|
return nil
|
|
|
|
}
|
2017-12-13 16:31:38 +00:00
|
|
|
|
|
|
|
// DetachBlockFCDisk detaches a volume from kubelet node, removes scsi device file
|
|
|
|
// such as /dev/sdX from the node, and then removes loopback for the scsi device.
|
|
|
|
func (util *FCUtil) DetachBlockFCDisk(c fcDiskUnmapper, mapPath, devicePath string) error {
|
|
|
|
// Check if devicePath is valid
|
|
|
|
if len(devicePath) != 0 {
|
|
|
|
if pathExists, pathErr := checkPathExists(devicePath); !pathExists || pathErr != nil {
|
|
|
|
return pathErr
|
|
|
|
}
|
|
|
|
} else {
|
2018-02-09 06:53:53 +00:00
|
|
|
// TODO: FC plugin can't obtain the devicePath from kubelet because devicePath
|
2017-12-13 16:31:38 +00:00
|
|
|
// in volume object isn't updated when volume is attached to kubelet node.
|
2018-02-09 06:53:53 +00:00
|
|
|
glog.Infof("fc: devicePath is empty. Try to retrieve FC configuration from global map path: %v", mapPath)
|
2017-12-13 16:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if global map path is valid
|
|
|
|
// global map path examples:
|
|
|
|
// wwn+lun: plugins/kubernetes.io/fc/volumeDevices/50060e801049cfd1-lun-0/
|
|
|
|
// wwid: plugins/kubernetes.io/fc/volumeDevices/3600508b400105e210000900000490000/
|
|
|
|
if pathExists, pathErr := checkPathExists(mapPath); !pathExists || pathErr != nil {
|
|
|
|
return pathErr
|
|
|
|
}
|
|
|
|
|
2018-02-09 06:53:53 +00:00
|
|
|
// Retrieve volume plugin dependent path like '50060e801049cfd1-lun-0' from global map path
|
2017-12-13 16:31:38 +00:00
|
|
|
arr := strings.Split(mapPath, "/")
|
|
|
|
if len(arr) < 1 {
|
2018-02-09 06:53:53 +00:00
|
|
|
return fmt.Errorf("Fail to retrieve volume plugin information from global map path: %v", mapPath)
|
2017-12-13 16:31:38 +00:00
|
|
|
}
|
|
|
|
volumeInfo := arr[len(arr)-1]
|
|
|
|
|
|
|
|
// Search symbolick link which matches volumeInfo under /dev/disk/by-path or /dev/disk/by-id
|
|
|
|
// then find destination device path from the link
|
|
|
|
searchPath := byID
|
|
|
|
if strings.Contains(volumeInfo, "-lun-") {
|
|
|
|
searchPath = byPath
|
|
|
|
}
|
|
|
|
fis, err := ioutil.ReadDir(searchPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, fi := range fis {
|
|
|
|
if strings.Contains(fi.Name(), volumeInfo) {
|
|
|
|
devicePath = path.Join(searchPath, fi.Name())
|
|
|
|
glog.V(5).Infof("fc: updated devicePath: %s", devicePath)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(devicePath) == 0 {
|
|
|
|
return fmt.Errorf("fc: failed to find corresponding device from searchPath: %v", searchPath)
|
|
|
|
}
|
|
|
|
dstPath, err := c.io.EvalSymlinks(devicePath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
glog.V(4).Infof("fc: find destination device path from symlink: %v", dstPath)
|
|
|
|
|
|
|
|
// Get loopback device which takes fd lock for device beofore detaching a volume from node.
|
2018-01-17 15:18:51 +00:00
|
|
|
// TODO: This is a workaround for issue #54108
|
|
|
|
// Currently local attach plugins such as FC, iSCSI, RBD can't obtain devicePath during
|
|
|
|
// GenerateUnmapDeviceFunc() in operation_generator. As a result, these plugins fail to get
|
|
|
|
// and remove loopback device then it will be remained on kubelet node. To avoid the problem,
|
|
|
|
// local attach plugins needs to remove loopback device during TearDownDevice().
|
2017-12-13 16:31:38 +00:00
|
|
|
var devices []string
|
2018-02-06 08:38:41 +00:00
|
|
|
blkUtil := volumepathhandler.NewBlockVolumePathHandler()
|
2017-12-13 16:31:38 +00:00
|
|
|
dm := c.deviceUtil.FindMultipathDeviceForDevice(dstPath)
|
|
|
|
if len(dm) != 0 {
|
|
|
|
dstPath = dm
|
|
|
|
}
|
2018-02-06 08:38:41 +00:00
|
|
|
loop, err := volumepathhandler.BlockVolumePathHandler.GetLoopDevice(blkUtil, dstPath)
|
2017-12-13 16:31:38 +00:00
|
|
|
if err != nil {
|
2018-02-06 08:38:41 +00:00
|
|
|
if err.Error() != volumepathhandler.ErrDeviceNotFound {
|
2018-01-30 22:56:53 +00:00
|
|
|
return fmt.Errorf("fc: failed to get loopback for destination path: %v, err: %v", dstPath, err)
|
|
|
|
}
|
2018-06-29 19:38:52 +00:00
|
|
|
glog.Warningf("fc: loopback for destination path: %s not found", dstPath)
|
2017-12-13 16:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Detach volume from kubelet node
|
|
|
|
if len(dm) != 0 {
|
|
|
|
// Find all devices which are managed by multipath
|
|
|
|
devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dm)
|
|
|
|
} else {
|
|
|
|
// Add single device path to devices
|
|
|
|
devices = append(devices, dstPath)
|
|
|
|
}
|
|
|
|
var lastErr error
|
|
|
|
for _, device := range devices {
|
|
|
|
err = util.detachFCDisk(c.io, device)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
|
|
|
|
lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if lastErr != nil {
|
|
|
|
glog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
|
|
|
|
return lastErr
|
|
|
|
}
|
2018-01-30 22:56:53 +00:00
|
|
|
if len(loop) != 0 {
|
|
|
|
// The volume was successfully detached from node. We can safely remove the loopback.
|
2018-02-06 08:38:41 +00:00
|
|
|
err = volumepathhandler.BlockVolumePathHandler.RemoveLoopDevice(blkUtil, loop)
|
2018-01-30 22:56:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("fc: failed to remove loopback :%v, err: %v", loop, err)
|
|
|
|
}
|
2017-12-13 16:31:38 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkPathExists(path string) (bool, error) {
|
|
|
|
if pathExists, pathErr := volumeutil.PathExists(path); pathErr != nil {
|
|
|
|
return pathExists, fmt.Errorf("Error checking if path exists: %v", pathErr)
|
|
|
|
} else if !pathExists {
|
|
|
|
glog.Warningf("Warning: Unmap skipped because path does not exist: %v", path)
|
|
|
|
return pathExists, nil
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|