123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- /*
- Copyright 2015 The Kubernetes Authors.
- 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/filepath"
- "strconv"
- "strings"
- "k8s.io/klog"
- "k8s.io/utils/mount"
- v1 "k8s.io/api/core/v1"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- "k8s.io/kubernetes/pkg/features"
- "k8s.io/kubernetes/pkg/volume"
- volumeutil "k8s.io/kubernetes/pkg/volume/util"
- )
- 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{}
- const (
- byPath = "/dev/disk/by-path/"
- byID = "/dev/disk/by-id/"
- )
- 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
- func findDisk(wwn, lun string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
- fcPath := "-fc-0x" + wwn + "-lun-" + lun
- devPath := byPath
- if dirs, err := io.ReadDir(devPath); err == nil {
- for _, f := range dirs {
- name := f.Name()
- if strings.Contains(name, fcPath) {
- if disk, err1 := io.EvalSymlinks(devPath + name); err1 == nil {
- dm := deviceUtil.FindMultipathDeviceForDevice(disk)
- klog.Infof("fc: find disk: %v, dm: %v", disk, dm)
- return disk, dm
- }
- }
- }
- }
- return "", ""
- }
- // given a wwid, find the device and associated devicemapper parent
- func findDiskWWIDs(wwid string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
- // 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.
- fcPath := "scsi-" + wwid
- devID := byID
- if dirs, err := io.ReadDir(devID); err == nil {
- for _, f := range dirs {
- name := f.Name()
- if name == fcPath {
- disk, err := io.EvalSymlinks(devID + name)
- if err != nil {
- klog.V(2).Infof("fc: failed to find a corresponding disk from symlink[%s], error %v", devID+name, err)
- return "", ""
- }
- dm := deviceUtil.FindMultipathDeviceForDevice(disk)
- klog.Infof("fc: find disk: %v, dm: %v", disk, dm)
- return disk, dm
- }
- }
- }
- klog.V(2).Infof("fc: failed to find a disk [%s]", devID+fcPath)
- return "", ""
- }
- // Removes a scsi device based upon /dev/sdX name
- func removeFromScsiSubsystem(deviceName string, io ioHandler) {
- fileName := "/sys/block/" + deviceName + "/device/delete"
- klog.V(4).Infof("fc: remove device from scsi-subsystem: path: %s", fileName)
- data := []byte("1")
- io.WriteFile(fileName, data, 0666)
- }
- // rescan scsi bus
- func scsiHostRescan(io ioHandler) {
- scsiPath := "/sys/class/scsi_host/"
- if dirs, err := io.ReadDir(scsiPath); err == nil {
- for _, f := range dirs {
- name := scsiPath + f.Name() + "/scan"
- data := []byte("- - -")
- io.WriteFile(name, data, 0666)
- }
- }
- }
- // make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/target1-target2-lun-0
- func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
- if len(wwns) != 0 {
- w := strings.Join(wwns, "-")
- return filepath.Join(host.GetPluginDir(fcPluginName), w+"-lun-"+lun)
- }
- return filepath.Join(host.GetPluginDir(fcPluginName), strings.Join(wwids, "-"))
- }
- // 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 {
- w := strings.Join(wwns, "-")
- return filepath.Join(host.GetVolumeDevicePluginDir(fcPluginName), w+"-lun-"+lun)
- }
- return filepath.Join(host.GetVolumeDevicePluginDir(fcPluginName), strings.Join(wwids, "-"))
- }
- func parsePDName(path string) (wwns []string, lun int32, wwids []string, err error) {
- // parse directory name created by makePDNameInternal or makeVDPDNameInternal
- dirname := filepath.Base(path)
- components := strings.Split(dirname, "-")
- l := len(components)
- if l == 1 {
- // No '-', it must be single WWID
- return nil, 0, components, nil
- }
- if components[l-2] == "lun" {
- // it has -lun-, it's list of WWNs + lun number as the last component
- if l == 2 {
- return nil, 0, nil, fmt.Errorf("no wwn in: %s", dirname)
- }
- lun, err := strconv.Atoi(components[l-1])
- if err != nil {
- return nil, 0, nil, err
- }
- return components[:l-2], int32(lun), nil, nil
- }
- // no -lun-, it's just list of WWIDs
- return nil, 0, components, nil
- }
- type fcUtil struct{}
- func (util *fcUtil) MakeGlobalPDName(fc fcDisk) string {
- return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
- }
- // 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) {
- 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
- }
- 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 {
- for _, diskID := range diskIDs {
- if len(wwns) != 0 {
- disk, dm = findDisk(diskID, lun, io, b.deviceUtil)
- } else {
- disk, dm = findDiskWWIDs(diskID, io, b.deviceUtil)
- }
- // 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 == "" {
- return "", fmt.Errorf("no fc disk found")
- }
- // if multipath devicemapper device is found, use it; otherwise use raw disk
- if dm != "" {
- return dm, nil
- }
- 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.
- klog.V(5).Infof("fc: AttachDisk volumeMode: %s, devicePath: %s", b.volumeMode, devicePath)
- if b.volumeMode == v1.PersistentVolumeBlock {
- return devicePath, nil
- }
- }
- // mount it
- globalPDPath := util.MakeGlobalPDName(*b.fcDisk)
- if err := os.MkdirAll(globalPDPath, 0750); err != nil {
- return devicePath, fmt.Errorf("fc: failed to mkdir %s, error", globalPDPath)
- }
- noMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
- if err != nil {
- return devicePath, fmt.Errorf("Heuristic determination of mount point failed:%v", err)
- }
- if !noMnt {
- klog.Infof("fc: %s already mounted", globalPDPath)
- return devicePath, nil
- }
- err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, b.mountOptions)
- if err != nil {
- return devicePath, fmt.Errorf("fc: failed to mount fc volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err)
- }
- return devicePath, err
- }
- // 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)
- }
- klog.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 {
- klog.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 {
- klog.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 {
- // Remove scsi device from the node.
- if !strings.HasPrefix(devicePath, "/dev/") {
- return fmt.Errorf("fc detach disk: invalid device name: %s", devicePath)
- }
- arr := strings.Split(devicePath, "/")
- dev := arr[len(arr)-1]
- removeFromScsiSubsystem(dev, io)
- return nil
- }
- // 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 {
- // TODO: FC plugin can't obtain the devicePath from kubelet because devicePath
- // in volume object isn't updated when volume is attached to kubelet node.
- klog.Infof("fc: devicePath is empty. Try to retrieve FC configuration from global map path: %v", mapPath)
- }
- // 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
- }
- // Retrieve volume plugin dependent path like '50060e801049cfd1-lun-0' from global map path
- arr := strings.Split(mapPath, "/")
- if len(arr) < 1 {
- return fmt.Errorf("Fail to retrieve volume plugin information from global map path: %v", mapPath)
- }
- volumeInfo := arr[len(arr)-1]
- // Search symbolic 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 = filepath.Join(searchPath, fi.Name())
- klog.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
- }
- klog.V(4).Infof("fc: find destination device path from symlink: %v", dstPath)
- var devices []string
- dm := c.deviceUtil.FindMultipathDeviceForDevice(dstPath)
- if len(dm) != 0 {
- dstPath = dm
- }
- // 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 {
- klog.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 {
- klog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
- return lastErr
- }
- return nil
- }
- func checkPathExists(path string) (bool, error) {
- if pathExists, pathErr := mount.PathExists(path); pathErr != nil {
- return pathExists, fmt.Errorf("Error checking if path exists: %v", pathErr)
- } else if !pathExists {
- klog.Warningf("Warning: Unmap skipped because path does not exist: %v", path)
- return pathExists, nil
- }
- return true, nil
- }
|