#
# iscsi.py - iscsi class
#
# Copyright (C) 2005, 2006 IBM, Inc. All rights reserved.
# Copyright (C) 2006 Red Hat, Inc. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
from constants import *
import os
import errno
import string
import signal
import iutil
import isys
from flags import flags
import logging
import shutil
import time
import md5, random
import partedUtils
log = logging.getLogger("anaconda")
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
# Note that stage2 copies all files under /sbin to /usr/sbin
global ISCSID
ISCSID=""
global ISCSIADM
ISCSIADM = ""
INITIATOR_FILE="/etc/iscsi/initiatorname.iscsi"
ISCSID_CONF="/etc/iscsi/iscsid.conf"
def find_iscsi_files():
global ISCSID
if ISCSID == "":
for dir in ("/usr/sbin", "/tmp/updates", "/mnt/source/RHupdates"):
path="%s/iscsid" % (dir,)
if os.access(path, os.X_OK):
ISCSID=path
global ISCSIADM
if ISCSIADM == "":
for dir in ("/usr/sbin", "/tmp/updates", "/mnt/source/RHupdates"):
path="%s/iscsiadm" % (dir,)
if os.access(path, os.X_OK):
ISCSIADM=path
def has_iscsi():
find_iscsi_files()
if ISCSID == "" or ISCSIADM == "":
return False
log.info("ISCSID is %s" % (ISCSID,))
log.info("ISCSIADM is %s" % (ISCSIADM,))
# make sure the module is loaded
if not os.access("/sys/module/iscsi_tcp", os.X_OK):
return False
return True
def iscsi_get_node_record(node_settings, record):
for line in node_settings:
if line.startswith(record):
words = line.split(" = ")
if len(words) == 2:
return words[1]
# should never happen but better safe then sorry
break
return None
def iscsi_make_node_autostart(disk):
sysfs_path = os.path.realpath("/sys/block/%s/device" %(disk,))
argv = [ "-m", "session", "-r", sysfs_path ]
log.debug("iscsiadm %s" %(string.join(argv),))
node_settings = iutil.execWithCapture(ISCSIADM, argv).splitlines()
node_name = iscsi_get_node_record(node_settings, "node.name")
argv = [ "-m", "node", "-T", node_name, "-o", "update", "-n",
"node.startup", "-v", "automatic" ]
log.debug("iscsiadm %s" %(string.join(argv),))
iutil.execWithRedirect(ISCSIADM, argv,
stdout = "/dev/tty5", stderr="/dev/tty5")
class iscsiTarget:
def __init__(self, ipaddr, port=None, user=None, pw=None,
user_in=None, pw_in=None):
# FIXME: validate ipaddr
self.ipaddr = ipaddr
if not port: # FIXME: hack hack hack
port = 3260
self.port = str(port)
self.user = user
self.password = pw
self.user_in = user_in
self.password_in = pw_in
self._portal = None
self._nodes = []
find_iscsi_files()
def _getPortal(self):
if self._portal is None:
argv = [ "-m", "discovery", "-t", "st", "-p", self.ipaddr ]
log.debug("iscsiadm %s" %(string.join(argv),))
records = iutil.execWithCapture(ISCSIADM, argv)
records = records.strip()
for line in records.split("\n"):
log.debug(" %s" % (line,))
if not line or line.find("found!") != -1:
log.warn("no record found!")
continue
pnlist = line.split()
if len(pnlist) != 2:
log.warn("didn't get what we expected from iscsiadm")
continue
(portal, node) = pnlist
if portal.startswith(self.ipaddr):
self._portal = portal
self._nodes.append(node)
return self._portal
portal = property(_getPortal)
def _getNode(self):
if len(self._nodes) == 0:
# _getPortal() fills the list, if possible.
self._getPortal()
return self._nodes
nodes = property(_getNode)
def discover(self):
argv = [ "-m", "discovery", "-t", "st", "-p",
"%s:%s" % (self.ipaddr, self.port) ]
log.debug("iscsiadm %s" %(string.join(argv),))
rc = iutil.execWithRedirect(ISCSIADM, argv,
stdout = "/dev/tty5", stderr="/dev/tty5")
if rc != 0:
log.warn("iscsiadm failed to discover on %s" %(self.ipaddr,))
return False
return True
def addNode(self, node):
if node is None or self.portal is None:
log.warn("unable to find portal information")
return
argv = [ "-m", "node", "-T", node, "-p", self.portal,
"-o", "new" ]
log.debug("iscsiadm %s" %(string.join(argv),))
iutil.execWithRedirect(ISCSIADM, argv,
stdout = "/dev/tty5", stderr="/dev/tty5")
def loginToNode(self, node):
if node is None or self.portal is None:
log.warn("unable to find portal information")
return
argv = [ "-m", "node", "-T", node, "-p", self.portal, "--login" ]
log.debug("iscsiadm %s" %(string.join(argv),))
rc = iutil.execWithRedirect(ISCSIADM, argv,
stdout = "/dev/tty5", stderr="/dev/tty5")
if rc != 0:
log.warn("iscsiadm failed to login to %s" %(self.ipaddr,))
return False
return True
def login(self):
if len(self.nodes) == 0 or self.portal is None:
log.warn("unable to find portal information")
return False
ret = False
for node in self.nodes:
if self.loginToNode(node):
ret = True
self.addNode(node)
# we return True if there were any successful logins for our portal.
return ret
def logout(self):
for node in self.nodes:
argv = [ "-m", "node", "-T", node, "-p", self.portal, "--logout" ]
log.debug("iscsiadm %s" %(string.join(argv),))
rc = iutil.execWithRedirect(ISCSIADM, argv,
stdout = "/dev/tty5", stderr="/dev/tty5")
def randomIname():
"""Generate a random initiator name the same way as iscsi-iname"""
s = "iqn.2005-03.com.max:01."
m = md5.md5()
u = os.uname()
for i in u:
m.update(i)
dig = m.hexdigest()
for i in range(0, 6):
s += dig[random.randrange(0, 32)]
return s
class iscsi(object):
def __init__(self):
self.fwinfo = self._queryFirmware()
self.targets = []
self._initiator = ""
self.initiatorSet = False
self.oldInitiatorFile = None
self.iscsidStarted = False
if self.fwinfo and self.fwinfo.has_key("iface.initiatorname"):
self._initiator = self.fwinfo["iface.initiatorname"]
self.initiatorSet = True
def _getInitiator(self):
if self._initiator != "":
return self._initiator
if self.fwinfo and self.fwinfo.has_key("iface.initiatorname"):
return self.fwinfo["iface.initiatorname"]
else:
return randomIname()
def _setInitiator(self, val):
if self._initiator != "" and val != self._initiator:
raise ValueError, "Unable to change iSCSI initiator name once set"
if len(val) == 0:
raise ValueError, "Must provide a non-zero length string"
self._initiator = val
self.initiatorSet = True
initiator = property(_getInitiator, _setInitiator)
def _queryFirmware(self):
# Example:
# [root@elm3b87 ~]# iscsiadm -m fw
# iface.initiatorname = iqn.2007-05.com.ibm.beaverton.elm3b87:01
# iface.hwaddress = 00:14:5e:b3:8e:b2
# node.name = iqn.1992-08.com.netapp:sn.84183797
# node.conn[0].address = 9.47.67.152
# node.conn[0].port = 3260
find_iscsi_files()
if not has_iscsi():
return
retval = {}
argv = [ "-m", "fw" ]
log.debug("queryFirmware: ISCSIADM is %s" % (ISCSIADM,))
result = iutil.execWithCapture(ISCSIADM, argv)
result = result.strip()
if len(result) == 0 \
or result[0].find("iscsiadm -") != -1 \
or result[0].find("iscsiadm: ") != -1:
log.debug("queryFirmware: iscsiadm %s returns bad output: %s" %
(argv,result))
# Try querying the node records instead
argv = [ "-m", "node", "-o", "show", "-S" ]
result = iutil.execWithCapture(ISCSIADM, argv)
if len(result) == 0 \
or result[0].find("iscsiadm -") != -1 \
or result[0].find("iscsiadm: ") != -1:
log.debug("queryFirmware: iscsiadm %s returns bad output: %s" %
(argv,result))
return retval
for line in result.split("\n"):
SPLIT = " = "
idx = line.find(SPLIT)
if idx != -1:
lhs = line[:idx]
rhs = line[idx+len(SPLIT):]
retval[lhs] = rhs
return retval
def _startIscsiDaemon(self):
psout = iutil.execWithCapture("/usr/bin/pidof", ["iscsid"])
if psout.strip() == "":
log.info("iSCSI startup")
iutil.execWithRedirect(ISCSID, [],
stdout="/dev/tty5", stderr="/dev/tty5")
self.iscsidStarted = True
time.sleep(2)
def _stopIscsiDaemon(self):
result = iutil.execWithCapture(ISCSIADM, ["-k", "0"])
result.strip()
if result == "":
return
psout = iutil.execWithCapture("/usr/bin/pidof", ["iscsid"])
if psout.strip() != "":
log.info("iSCSI shutdown")
for t in self.targets:
t.logout()
for pidstr in psout.split():
pid = string.atoi(pidstr)
login.info("killing %s %d" % (ISCSID, pid))
os.kill(pid, signal.SIGKILL)
self.iscsidStarted = False
def shutdown(self):
if not has_iscsi():
return
if flags.test:
if self.oldInitiatorFile != None:
f = open(INITIATOR_FILE, "w")
for line in self.oldInitiatorFile:
f.write(line)
f.close ()
self.oldInitiatorFile = None
self._stopIscsiDaemon()
def loginToDefaultDrive(self):
# Example:
# [root@elm3b87 ~]# iscsiadm -m discovery -t fw -l
# Logging in to [iface: default, target: iqn.1992-08.com.netapp:sn.84183797, portal: 9.47.67.152,3260]
find_iscsi_files()
argv = [ "-m", "discovery", "-t", "fw", "-l" ]
result = iutil.execWithCapture(ISCSIADM, argv)
log.debug("iscsiadm result: %s" % (result,))
start = result.find('[')
end = result.rfind(']')
if start == -1 or end == -1:
log.warn("could not find markers. iscsiadm returned: %s" %
(result,))
return
values = {}
for kv in string.split(result[start+1:end], ', '):
(k, v) = string.split(kv, ': ')
values[k] = v
del start, end
if not values.has_key('target'):
log.warn("iBFT data missing target. iscsiadm returned: %s" %
(result,))
if not values.has_key('portal'):
log.warn("iBFT data missing portal. iscsiadm returned: %s" %
(result,))
else:
portal = values['portal']
comma = portal.find(',')
if comma == -1:
values['port'] = 3260
else:
values['port'] = portal[comma+1:]
values['portal'] = portal[0:comma]
if not values.has_key('chap-username') or not \
values.has_key('chap-password'):
if values.has_key('chap-username'):
log.warn("Invalid iBFT CHAP password. iscsiadm returned: %s" %
(result,))
return
if values.has_key('chap-password'):
log.warn("Invalid iBFT CHAP username. iscsiadm returned: %s" %
(result,))
return
if not values.has_key('rev-chap-username') or not \
values.has_key('rev-chap-password'):
if values.has_key('rev-chap-username'):
log.warn("Invalid iBFT Reverse CHAP password. " \
"iscsiadm returned %s" % (result,))
return
if values.has_key('rev-chap-password'):
log.warn("Invalid iBFT Reverse CHAP username. " \
"iscsiadm returned %s" % (result,))
return
target = values['target']
renames = {
'portal': 'ipaddr',
'chap-username': 'user',
'chap-password': 'pw',
'rev-chap-username': 'user_in',
'rev-chap-password': 'pw_in',
}
for k,v in renames.items():
if values.has_key(k):
values[v] = values[k]
del values[k]
badKeys = filter(lambda x: not x in \
('ipaddr','port','user','pw','user_in','pw_in'),
values.keys())
for k in badKeys:
del values[k]
# make a new target
self.addTarget(**values)
def startIBFT(self):
# If there is a default drive in the iSCSI configuration, then
# automatically attach to it. Do this before testing the initiator
# name, because it is provided by the iBFT too
if flags.ibft:
self.loginToDefaultDrive()
def startup(self, intf = None):
if not has_iscsi():
return
if not self.initiatorSet:
log.info("no initiator set")
return
if flags.test:
if os.access(INITIATOR_FILE, os.R_OK):
f = open(INITIATOR_FILE, "r")
self.oldInitiatorFile = f.readlines()
f.close()
if intf:
w = intf.waitWindow(_("Initializing iSCSI initiator"),
_("Initializing iSCSI initiator"))
log.debug("Setting up %s" % (INITIATOR_FILE, ))
log.info("iSCSI initiator name %s", self.initiator)
if os.path.exists(INITIATOR_FILE):
os.unlink(INITIATOR_FILE)
if not os.path.isdir("/etc/iscsi"):
os.makedirs("/etc/iscsi", 0755)
fd = os.open(INITIATOR_FILE, os.O_RDWR | os.O_CREAT)
os.write(fd, "InitiatorName=%s\n" %(self.initiator))
os.close(fd)
if not os.path.isdir("/var/lib/iscsi"):
os.makedirs("/var/lib/iscsi", 0660)
for dir in ['nodes','send_targets','ifaces']:
fulldir = "/var/lib/iscsi/%s" % (dir,)
if not os.path.isdir(fulldir):
os.makedirs(fulldir, 0660)
self._startIscsiDaemon()
for t in self.targets:
if not t.discover():
continue
t.login()
if intf:
w.pop()
def addTarget(self, ipaddr, port="3260", user=None, pw=None,
user_in=None, pw_in=None, intf=None):
if not self.iscsidStarted:
self.startup(intf)
if not self.iscsidStarted:
# can't start for some reason.... just fallback I guess
return
commentUser = '#'
commentUser_in = '#'
if user is not None or pw is not None:
commentUser = ''
if user is None:
raise ValueError, "user is required"
if pw is None:
raise ValueError, "pw is required"
if user_in is not None or pw_in is not None:
commentUser_in = ''
if user_in is None:
raise ValueError, "user_in is required"
if pw_in is None:
raise ValueError, "pw_in is required"
# If either a user/pw pair was specified or a user_in/pw_in was
# specified, then CHAP is specified.
if commentUser == '' or commentUser_in == '':
commentChap = ''
else:
commentChap = '#'
oldIscsidFile = []
try:
f = open(ISCSID_CONF, "r")
oldIscsidFile = f.readlines()
f.close()
except IOError, x:
if x.errno != errno.ENOENT:
raise RuntimeError, "Cannot open %s for read." % (ISCSID_CONF,)
try:
f = open(ISCSID_CONF, "w")
except:
raise RuntimeError, "Cannot open %s for write." % (ISCSID_CONF,)
vals = {
"node.session.auth.authmethod = ": [commentChap, "CHAP"],
"node.session.auth.username = ": [commentUser, user],
"node.session.auth.password = ": [commentUser, pw],
"node.session.auth.username_in = ": [commentUser_in, user_in],
"node.session.auth.password_in = ": [commentUser_in, pw_in],
"discovery.sendtargets.auth.authmethod = ": [commentChap, "CHAP"],
"discovery.sendtargets.auth.username = ": [commentUser, user],
"discovery.sendtargets.auth.password = ": [commentUser, pw],
"discovery.sendtargets.auth.username_in = ":
[commentUser_in, user_in],
"discovery.sendtargets.auth.password_in = ":
[commentUser_in, pw_in],
}
for line in oldIscsidFile:
s = line.strip()
# grab the cr/lf/cr+lf
nl = line[line.find(s)+len(s):]
found = False
for (k, (c, v)) in vals.items():
if line.find(k) != -1:
f.write("%s%s%s%s" % (c, k, v, nl))
found=True
del vals[k]
break
if not found:
f.write(line)
for (k, (c, v)) in vals.items():
f.write("%s%s%s\n" % (c, k, v))
f.close ()
t = iscsiTarget(ipaddr, port, user, pw, user_in, pw_in)
if not t.discover():
return
if not t.login():
return
self.targets.append(t)
return
def writeKS(self, f):
if not self.initiatorSet:
return
f.write("iscsiname %s\n" %(self.initiator,))
for t in self.targets:
f.write("iscsi --ipaddr %s --port %s" %(t.ipaddr, t.port))
if t.user:
f.write(" --user %s" %(t.user,))
if t.password:
f.write(" --password %s" %(t.password,))
if t.user_in:
f.write(" --reverse-user %s" % (t.user_in,))
if t.password_in:
f.write(" --reverse-password %s" % (t.password_in,))
f.write("\n")
def write(self, instPath, anaconda):
if not self.initiatorSet:
return
if not flags.test:
root_drives = [ ]
req = anaconda.id.partitions.getRequestByMountPoint("/")
root_requests = anaconda.id.partitions.getUnderlyingRequests(req)
for req in root_requests:
# req.drive is unreliable so figure it out ourselves
part = partedUtils.get_partition_by_name(anaconda.id.diskset.disks,
req.device)
if not part:
continue
drive = partedUtils.get_partition_drive(part)
if drive not in root_drives:
root_drives.append(drive)
log.debug("iscsi.write: root_drives: %s" % (string.join(root_drives),))
# set iscsi nodes not used for root to autostart
for disk in anaconda.id.diskset.disks.keys():
if isys.driveIsIscsi(disk) and not disk in root_drives:
iscsi_make_node_autostart(disk)
if not os.path.isdir(instPath + "/etc/iscsi"):
os.makedirs(instPath + "/etc/iscsi", 0755)
fd = os.open(instPath + INITIATOR_FILE, os.O_RDWR | os.O_CREAT)
os.write(fd, "InitiatorName=%s\n" %(self.initiator))
os.close(fd)
# copy "db" files. *sigh*
if not os.path.isdir(instPath + "/var/lib/iscsi"):
os.makedirs(instPath + "/var/lib/iscsi", 0755)
for d in ("/var/lib/iscsi/nodes", "/var/lib/iscsi/send_targets"):
if os.path.isdir(d):
shutil.copytree(d, instPath + d)
# vim:tw=78:ts=4:et:sw=4