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"
2016-07-25 04:18:38 +00:00
"regexp"
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 )
2016-07-25 04:18:38 +00:00
func waitForPathToExist ( devicePath string , maxRetries int , deviceTransport string ) bool {
2015-11-05 19:06:20 +00:00
// This makes unit testing a lot easier
2016-07-25 04:18:38 +00:00
return waitForPathToExistInternal ( devicePath , maxRetries , deviceTransport , os . Stat , filepath . Glob )
2015-11-05 19:06:20 +00:00
}
2016-07-25 04:18:38 +00:00
func waitForPathToExistInternal ( devicePath string , maxRetries int , deviceTransport 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
2016-07-25 04:18:38 +00:00
if deviceTransport == "tcp" {
2015-11-05 19:06:20 +00:00
_ , 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
}
2016-12-21 09:52:01 +00:00
if i == maxRetries - 1 {
break
}
2015-03-13 21:31:13 +00:00
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
}
2016-12-23 14:42:13 +00:00
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/iscsi/iface_name/portal-some_iqn-lun-lun_id
func makePDNameInternal ( host volume . VolumeHost , portal string , iqn string , lun string , iface string ) string {
return path . Join ( host . GetPluginDir ( iscsiPluginName ) , iface , portal + "-" + iqn + "-lun-" + lun )
2015-03-13 21:31:13 +00:00
}
type ISCSIUtil struct { }
func ( util * ISCSIUtil ) MakeGlobalPDName ( iscsi iscsiDisk ) string {
2016-12-23 14:42:13 +00:00
return makePDNameInternal ( iscsi . plugin . host , iscsi . portals [ 0 ] , iscsi . iqn , iscsi . lun , iscsi . iface )
2015-03-13 21:31:13 +00:00
}
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
2017-02-06 11:36:33 +00:00
var devicePaths [ ] string
2016-07-25 04:18:38 +00:00
var iscsiTransport string
2016-12-23 14:42:13 +00:00
2016-07-25 04:18:38 +00:00
out , err := b . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "iface" , "-I" , b . iface , "-o" , "show" } )
if err != nil {
glog . Errorf ( "iscsi: could not read iface %s error: %s" , b . iface , string ( out ) )
return err
}
iscsiTransport = extractTransportname ( string ( out ) )
2016-12-23 14:42:13 +00:00
2017-02-06 11:36:33 +00:00
bkpPortal := b . portals
for _ , tp := range bkpPortal {
2016-12-23 14:42:13 +00:00
// Rescan sessions to discover newly mapped LUNs. Do not specify the interface when rescanning
// to avoid establishing additional sessions to the same target.
out , err := b . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "node" , "-p" , tp , "-T" , b . iqn , "-R" } )
if err != nil {
glog . Errorf ( "iscsi: failed to rescan session with error: %s (%v)" , string ( out ) , err )
}
2017-02-06 11:36:33 +00:00
if iscsiTransport == "" {
glog . Errorf ( "iscsi: could not find transport name in iface %s" , b . iface )
return errors . New ( fmt . Sprintf ( "Could not parse iface file for %s" , b . iface ) )
} else if iscsiTransport == "tcp" {
devicePath = strings . Join ( [ ] string { "/dev/disk/by-path/ip" , tp , "iscsi" , b . iqn , "lun" , b . lun } , "-" )
} else {
devicePath = strings . Join ( [ ] string { "/dev/disk/by-path/pci" , "*" , "ip" , tp , "iscsi" , b . iqn , "lun" , b . lun } , "-" )
2015-03-13 21:31:13 +00:00
}
2017-02-06 11:36:33 +00:00
exist := waitForPathToExist ( devicePath , 1 , iscsiTransport )
if exist == false {
// discover iscsi target
out , err := b . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "discovery" , "-t" , "sendtargets" , "-p" , tp , "-I" , b . iface } )
if err != nil {
glog . Errorf ( "iscsi: failed to sendtargets to portal %s error: %s" , tp , string ( out ) )
continue
}
// login to iscsi target
out , err = b . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "node" , "-p" , tp , "-T" , b . iqn , "-I" , b . iface , "--login" } )
if err != nil {
glog . Errorf ( "iscsi: failed to attach disk:Error: %s (%v)" , string ( out ) , err )
continue
}
exist = waitForPathToExist ( devicePath , 10 , iscsiTransport )
if ! exist {
glog . Errorf ( "Could not attach disk: Timeout after 10s" )
} else {
devicePaths = append ( devicePaths , devicePath )
}
} else {
glog . V ( 4 ) . Infof ( "iscsi: devicepath (%s) exists" , devicePath )
devicePaths = append ( devicePaths , devicePath )
2015-03-13 21:31:13 +00:00
}
}
2017-02-06 11:36:33 +00:00
if len ( devicePaths ) == 0 {
glog . Errorf ( "iscsi: failed to get any path for iscsi disk" )
return errors . New ( "failed to get any path for iscsi disk" )
}
//Make sure we use a valid devicepath to find mpio device.
devicePath = devicePaths [ 0 ]
2015-03-13 21:31:13 +00:00
// 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
}
2017-02-06 11:36:33 +00:00
for _ , path := range devicePaths {
// There shouldnt be any empty device paths. However adding this check
// for safer side to avoid the possibility of an empty entry.
if path == "" {
continue
}
// check if the dev is using mpio and if so mount it via the dm-XX device
if mappedDevicePath := b . deviceUtil . FindMultipathDeviceForDevice ( path ) ; mappedDevicePath != "" {
devicePath = mappedDevicePath
break
}
2016-04-19 05:10:00 +00:00
}
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 {
2016-12-23 14:42:13 +00:00
// this portal/iqn/iface are no longer referenced, log out
// extract iface from mount path
iface , err := extractIface ( mntPath )
if err != nil {
return err
}
2015-03-13 21:31:13 +00:00
// 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
}
2016-12-23 14:42:13 +00:00
glog . Infof ( "iscsi: log out target %s iqn %s iface %s" , portal , iqn , iface )
// logout may fail as no session may exist for the portal/IQN on the specified interface
out , err := c . plugin . execCommand ( "iscsiadm" , [ ] string { "-m" , "node" , "-p" , portal , "-T" , iqn , "-I" , iface , "--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
2016-07-25 04:18:38 +00:00
func extractTransportname ( ifaceOutput string ) ( iscsiTransport string ) {
re := regexp . MustCompile ( ` iface.transport_name = (.*)\n ` )
rex_output := re . FindStringSubmatch ( ifaceOutput )
if rex_output != nil {
iscsiTransport = rex_output [ 1 ]
} else {
return ""
}
// While iface.transport_name is a required parameter, handle it being unspecified anyways
if iscsiTransport == "<empty>" {
iscsiTransport = "tcp"
}
return iscsiTransport
}
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 ) : ]
2016-12-23 14:42:13 +00:00
// strip -lun- from mount path
ind = strings . LastIndex ( mntPath , "-lun-" )
2015-10-13 18:50:49 +00:00
if ind < 0 {
return "" , "" , fmt . Errorf ( "iscsi detach disk: malformatted mnt path: %s" , mntPath )
}
2016-12-23 14:42:13 +00:00
prefix := mntPath [ : ind ]
2015-10-13 18:50:49 +00:00
return device , prefix , nil
}
2016-12-23 14:42:13 +00:00
func extractIface ( mntPath string ) ( string , error ) {
ind := strings . LastIndex ( mntPath , "/" )
if ind < 0 {
return "" , fmt . Errorf ( "iscsi detach disk: malformatted mnt path: %s" , mntPath )
}
baseMntPath := mntPath [ : ind ]
ind = strings . LastIndex ( baseMntPath , "/" )
if ind < 0 {
return "" , fmt . Errorf ( "iscsi detach disk: malformatted mnt path: %s" , mntPath )
}
iface := baseMntPath [ ( ind + 1 ) : ]
return iface , nil
}
2015-10-13 18:50:49 +00:00
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
}