2015-03-13 21:31:13 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2015 The Kubernetes Authors .
2015-03-13 21:31:13 +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 iscsi
import (
"errors"
2015-10-13 18:50:49 +00:00
"fmt"
2015-03-13 21:31:13 +00:00
"os"
"path"
2015-11-05 19:06:20 +00:00
"path/filepath"
2015-03-13 21:31:13 +00:00
"strings"
"time"
2015-08-05 22:05:17 +00:00
"github.com/golang/glog"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
2015-03-13 21:31:13 +00:00
)
// stat a path, if not exists, retry maxRetries times
2015-11-05 19:06:20 +00:00
// when iscsi transports other than default are used, use glob instead as pci id of device is unknown
type StatFunc func ( string ) ( os . FileInfo , error )
type GlobFunc func ( string ) ( [ ] string , error )
func waitForPathToExist ( devicePath string , maxRetries int , deviceInterface string ) bool {
// This makes unit testing a lot easier
return waitForPathToExistInternal ( devicePath , maxRetries , deviceInterface , os . Stat , filepath . Glob )
}
func waitForPathToExistInternal ( devicePath string , maxRetries int , deviceInterface string , osStat StatFunc , filepathGlob GlobFunc ) bool {
2015-03-13 21:31:13 +00:00
for i := 0 ; i < maxRetries ; i ++ {
2015-11-05 19:06:20 +00:00
var err error
if deviceInterface == "default" {
_ , err = osStat ( devicePath )
} else {
fpath , _ := filepathGlob ( devicePath )
if fpath == nil {
err = os . ErrNotExist
}
}
2015-03-13 21:31:13 +00:00
if err == nil {
return true
}
if err != nil && ! os . IsNotExist ( err ) {
return false
}
time . Sleep ( time . Second )
}
return false
}
// getDevicePrefixRefCount: given a prefix of device path, find its reference count from /proc/mounts
// returns the reference count to the device and error code
// for services like iscsi construct multiple device paths with the same prefix pattern.
// this function aggregates all references to a service based on the prefix pattern
// More specifically, this prefix semantics is to aggregate disk paths that belong to the same iSCSI target/iqn pair.
// an iSCSI target could expose multiple LUNs through the same IQN, and Linux iSCSI initiator creates disk paths that start the same prefix but end with different LUN number
// When we decide whether it is time to logout a target, we have to see if none of the LUNs are used any more.
// That's where the prefix based ref count kicks in. If we only count the disks using exact match, we could log other disks out.
func getDevicePrefixRefCount ( mounter mount . Interface , deviceNamePrefix string ) ( int , error ) {
mps , err := mounter . List ( )
if err != nil {
return - 1 , err
}
// Find the number of references to the device.
refCount := 0
for i := range mps {
2015-10-13 18:50:49 +00:00
if strings . HasPrefix ( mps [ i ] . Path , deviceNamePrefix ) {
2015-03-13 21:31:13 +00:00
refCount ++
}
}
return refCount , nil
}
2015-10-08 08:21:17 +00:00
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/iscsi/portal-some_iqn-lun-lun_id
2015-03-13 21:31:13 +00:00
func makePDNameInternal ( host volume . VolumeHost , portal string , iqn string , lun string ) string {
2015-10-08 08:21:17 +00:00
return path . Join ( host . GetPluginDir ( iscsiPluginName ) , portal + "-" + iqn + "-lun-" + lun )
2015-03-13 21:31:13 +00:00
}
type ISCSIUtil struct { }
func ( util * ISCSIUtil ) MakeGlobalPDName ( iscsi iscsiDisk ) string {
return makePDNameInternal ( iscsi . plugin . host , iscsi . portal , iscsi . iqn , iscsi . lun )
}
2016-03-23 05:12:21 +00:00
func ( util * ISCSIUtil ) AttachDisk ( b iscsiDiskMounter ) error {
2015-11-05 19:06:20 +00:00
var devicePath string
if b . iface == "default" {
devicePath = strings . Join ( [ ] string { "/dev/disk/by-path/ip" , b . portal , "iscsi" , b . iqn , "lun" , b . lun } , "-" )
} else {
devicePath = strings . Join ( [ ] string { "/dev/disk/by-path/pci" , "*" , "ip" , b . portal , "iscsi" , b . iqn , "lun" , b . lun } , "-" )
}
exist := waitForPathToExist ( devicePath , 1 , b . iface )
2015-03-13 21:31:13 +00:00
if exist == false {
// discover iscsi target
2015-11-05 19:06:20 +00:00
out , err := b . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "discovery" , "-t" , "sendtargets" , "-p" , b . portal , "-I" , b . iface } )
2015-03-13 21:31:13 +00:00
if err != nil {
2015-07-24 08:55:56 +00:00
glog . Errorf ( "iscsi: failed to sendtargets to portal %s error: %s" , b . portal , string ( out ) )
2015-03-13 21:31:13 +00:00
return err
}
// login to iscsi target
2015-11-05 19:06:20 +00:00
out , err = b . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "node" , "-p" , b . portal , "-T" , b . iqn , "-I" , b . iface , "--login" } )
2015-03-13 21:31:13 +00:00
if err != nil {
2015-06-26 18:46:41 +00:00
glog . Errorf ( "iscsi: failed to attach disk:Error: %s (%v)" , string ( out ) , err )
2015-03-13 21:31:13 +00:00
return err
}
2015-11-05 19:06:20 +00:00
exist = waitForPathToExist ( devicePath , 10 , b . iface )
2015-03-13 21:31:13 +00:00
if ! exist {
return errors . New ( "Could not attach disk: Timeout after 10s" )
}
}
// mount it
2015-07-24 08:55:56 +00:00
globalPDPath := b . manager . MakeGlobalPDName ( * b . iscsiDisk )
2015-04-16 23:49:53 +00:00
notMnt , err := b . mounter . IsLikelyNotMountPoint ( globalPDPath )
if ! notMnt {
2015-03-13 21:31:13 +00:00
glog . Infof ( "iscsi: %s already mounted" , globalPDPath )
return nil
}
if err := os . MkdirAll ( globalPDPath , 0750 ) ; err != nil {
glog . Errorf ( "iscsi: failed to mkdir %s, error" , globalPDPath )
return err
}
2016-04-19 05:10:00 +00:00
// check if the dev is using mpio and if so mount it via the dm-XX device
if mappedDevicePath := b . deviceUtil . FindMultipathDeviceForDevice ( devicePath ) ; mappedDevicePath != "" {
devicePath = mappedDevicePath
}
2015-11-05 21:49:40 +00:00
err = b . mounter . FormatAndMount ( devicePath , globalPDPath , b . fsType , nil )
2015-03-13 21:31:13 +00:00
if err != nil {
2015-07-24 08:55:56 +00:00
glog . Errorf ( "iscsi: failed to mount iscsi volume %s [%s] to %s, error %v" , devicePath , b . fsType , globalPDPath , err )
2015-03-13 21:31:13 +00:00
}
return err
}
2016-03-23 05:12:21 +00:00
func ( util * ISCSIUtil ) DetachDisk ( c iscsiDiskUnmounter , mntPath string ) error {
2015-10-13 18:50:49 +00:00
_ , cnt , err := mount . GetDeviceNameFromMount ( c . mounter , mntPath )
2015-03-13 21:31:13 +00:00
if err != nil {
glog . Errorf ( "iscsi detach disk: failed to get device from mnt: %s\nError: %v" , mntPath , err )
return err
}
2015-07-24 08:55:56 +00:00
if err = c . mounter . Unmount ( mntPath ) ; err != nil {
2015-04-03 01:08:04 +00:00
glog . Errorf ( "iscsi detach disk: failed to unmount: %s\nError: %v" , mntPath , err )
2015-03-13 21:31:13 +00:00
return err
}
cnt --
// if device is no longer used, see if need to logout the target
if cnt == 0 {
2015-10-13 18:50:49 +00:00
device , prefix , err := extractDeviceAndPrefix ( mntPath )
if err != nil {
return err
}
2015-07-24 08:55:56 +00:00
refCount , err := getDevicePrefixRefCount ( c . mounter , prefix )
2015-03-13 21:31:13 +00:00
if err == nil && refCount == 0 {
// this portal/iqn are no longer referenced, log out
// extract portal and iqn from device path
2015-10-13 18:50:49 +00:00
portal , iqn , err := extractPortalAndIqn ( device )
if err != nil {
return err
}
2015-03-13 21:31:13 +00:00
glog . Infof ( "iscsi: log out target %s iqn %s" , portal , iqn )
2015-07-24 08:55:56 +00:00
out , err := c . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "node" , "-p" , portal , "-T" , iqn , "--logout" } )
2015-03-13 21:31:13 +00:00
if err != nil {
2015-06-26 18:46:41 +00:00
glog . Errorf ( "iscsi: failed to detach disk Error: %s" , string ( out ) )
2015-03-13 21:31:13 +00:00
}
}
}
return nil
}
2015-10-13 18:50:49 +00:00
func extractDeviceAndPrefix ( mntPath string ) ( string , string , error ) {
ind := strings . LastIndex ( mntPath , "/" )
if ind < 0 {
return "" , "" , fmt . Errorf ( "iscsi detach disk: malformatted mnt path: %s" , mntPath )
}
device := mntPath [ ( ind + 1 ) : ]
// strip -lun- from device path
ind = strings . LastIndex ( device , "-lun-" )
if ind < 0 {
return "" , "" , fmt . Errorf ( "iscsi detach disk: malformatted mnt path: %s" , mntPath )
}
prefix := device [ : ind ]
return device , prefix , nil
}
func extractPortalAndIqn ( device string ) ( string , string , error ) {
ind1 := strings . Index ( device , "-" )
if ind1 < 0 {
return "" , "" , fmt . Errorf ( "iscsi detach disk: no portal in %s" , device )
}
portal := device [ 0 : ind1 ]
ind2 := strings . Index ( device , "iqn." )
2015-11-24 08:13:59 +00:00
if ind2 < 0 {
ind2 = strings . Index ( device , "eui." )
}
2015-10-13 18:50:49 +00:00
if ind2 < 0 {
return "" , "" , fmt . Errorf ( "iscsi detach disk: no iqn in %s" , device )
}
ind := strings . LastIndex ( device , "-lun-" )
iqn := device [ ind2 : ind ]
return portal , iqn , nil
}