"""
Basic iscsi support for Linux host with the help of commands
iscsiadm and tgtadm.
This include the basic operates such as login and get device name by
target name. And it can support the real iscsi access and emulated
iscsi in localhost then access it.
"""
import re
import os
import logging
from autotest.client import os_dep
from autotest.client.shared import utils, error
from virttest import utils_selinux
from virttest import utils_net
ISCSI_CONFIG_FILE = "/etc/iscsi/initiatorname.iscsi"
[docs]def iscsi_get_sessions():
"""
Get the iscsi sessions activated
"""
cmd = "iscsiadm --mode session"
output = utils.system_output(cmd, ignore_status=True)
sessions = []
if "No active sessions" not in output:
for session in output.splitlines():
ip_addr = session.split()[2].split(',')[0]
target = session.split()[3]
sessions.append((ip_addr, target))
return sessions
[docs]def iscsi_get_nodes():
"""
Get the iscsi nodes
"""
cmd = "iscsiadm --mode node"
output = utils.system_output(cmd, ignore_status=True)
pattern = r"(\d+\.\d+\.\d+\.\d+|\[.+\]):\d+,\d+\s+([\w\.\-:\d]+)"
nodes = []
if "No records found" not in output:
nodes = re.findall(pattern, output)
return nodes
[docs]def iscsi_login(target_name, portal):
"""
Login to a target with the target name
:param target_name: Name of the target
:params portal: Hostname/Ip for iscsi server
"""
cmd = "iscsiadm --mode node --login --targetname %s" % target_name
cmd += " --portal %s" % portal
output = utils.system_output(cmd)
target_login = ""
if "successful" in output:
target_login = target_name
return target_login
[docs]def iscsi_node_del(target_name=None):
"""
Delete target node record, if the target name is not set then delete
all target node records.
:params target_name: Name of the target.
"""
node_list = iscsi_get_nodes()
cmd = ''
if target_name:
for node_tup in node_list:
if target_name in node_tup:
cmd = "iscsiadm -m node -o delete -T %s " % target_name
cmd += "--portal %s" % node_tup[0]
utils.system(cmd, ignore_status=True)
break
if not cmd:
logging.error("The target '%s' for delete is not in target node"
" record", target_name)
else:
for node_tup in node_list:
cmd = "iscsiadm -m node -o delete -T %s " % node_tup[1]
cmd += "--portal %s" % node_tup[0]
utils.system(cmd, ignore_status=True)
[docs]def iscsi_logout(target_name=None):
"""
Logout from a target. If the target name is not set then logout all
targets.
:params target_name: Name of the target.
"""
if target_name:
cmd = "iscsiadm --mode node --logout -T %s" % target_name
else:
cmd = "iscsiadm --mode node --logout all"
output = utils.system_output(cmd)
target_logout = ""
if "successful" in output:
target_logout = target_name
return target_logout
[docs]def iscsi_discover(portal_ip):
"""
Query from iscsi server for available targets
:param portal_ip: Ip for iscsi server
"""
cmd = "iscsiadm -m discovery -t sendtargets -p %s" % portal_ip
output = utils.system_output(cmd, ignore_status=True)
session = ""
if "Invalid" in output:
logging.debug(output)
else:
session = output
return session
class _IscsiComm(object):
"""
Provide an interface to complete the similar initialization
"""
def __init__(self, params, root_dir):
"""
common __init__ function used to initialize iSCSI service
:param params: parameters dict for iSCSI
:param root_dir: path for image
"""
self.target = params.get("target")
self.export_flag = False
self.luns = None
self.restart_tgtd = 'yes' == params.get("restart_tgtd", "no")
if params.get("portal_ip"):
self.portal_ip = params.get("portal_ip")
else:
self.portal_ip = "127.0.0.1"
if params.get("iscsi_thread_id"):
self.id = params.get("iscsi_thread_id")
else:
self.id = utils.generate_random_string(4)
self.initiator = params.get("initiator")
# CHAP AUTHENTICATION
self.chap_flag = False
self.chap_user = params.get("chap_user")
self.chap_passwd = params.get("chap_passwd")
if self.chap_user and self.chap_passwd:
self.chap_flag = True
if params.get("emulated_image"):
self.initiator = None
emulated_image = params.get("emulated_image")
self.emulated_image = os.path.join(root_dir, emulated_image)
self.device = "device.%s" % os.path.basename(self.emulated_image)
self.emulated_id = ""
self.emulated_size = params.get("image_size")
self.unit = self.emulated_size[-1].upper()
self.emulated_size = self.emulated_size[:-1]
# maps K,M,G,T => (count, bs)
emulated_size = {'K': (1, 1),
'M': (1, 1024),
'G': (1024, 1024),
'T': (1024, 1048576),
}
if emulated_size.has_key(self.unit):
block_size = emulated_size[self.unit][1]
size = int(self.emulated_size) * emulated_size[self.unit][0]
self.emulated_expect_size = block_size * size
self.create_cmd = ("dd if=/dev/zero of=%s count=%s bs=%sK"
% (self.emulated_image, size, block_size))
else:
self.device = None
def logged_in(self):
"""
Check if the session is login or not.
"""
sessions = iscsi_get_sessions()
login = False
if self.target in map(lambda x: x[1], sessions):
login = True
return login
def portal_visible(self):
"""
Check if the portal can be found or not.
"""
return bool(re.findall("%s$" % self.target,
iscsi_discover(self.portal_ip), re.M))
def set_initiatorName(self, id, name):
"""
back up and set up the InitiatorName
"""
if os.path.isfile("%s" % ISCSI_CONFIG_FILE):
logging.debug("Try to update iscsi initiatorname")
cmd = "mv %s %s-%s" % (ISCSI_CONFIG_FILE, ISCSI_CONFIG_FILE, id)
utils.system(cmd)
fd = open(ISCSI_CONFIG_FILE, 'w')
fd.write("InitiatorName=%s" % name)
fd.close()
utils.system("service iscsid restart")
def login(self):
"""
Login session for both real iscsi device and emulated iscsi.
Include env check and setup.
"""
login_flag = False
if self.portal_visible():
login_flag = True
elif self.initiator:
self.set_initiatorName(id=self.id, name=self.initiator)
if self.portal_visible():
login_flag = True
elif self.emulated_image:
self.export_target()
# If both iSCSI server and iSCSI client are on localhost.
# It's necessary to set up the InitiatorName.
if "127.0.0.1" in self.portal_ip:
self.set_initiatorName(id=self.id, name=self.target)
if self.portal_visible():
login_flag = True
if login_flag:
iscsi_login(self.target, self.portal_ip)
def get_device_name(self):
"""
Get device name from the target name.
"""
cmd = "iscsiadm -m session -P 3"
device_name = ""
if self.logged_in():
output = utils.system_output(cmd)
pattern = r"Target:\s+%s.*?disk\s(\w+)\s+\S+\srunning" % self.target
device_name = re.findall(pattern, output, re.S)
try:
device_name = "/dev/%s" % device_name[0]
except IndexError:
logging.error("Can not find target '%s' after login.", self.target)
else:
logging.error("Session is not logged in yet.")
return device_name
def set_chap_auth_initiator(self):
"""
Set CHAP authentication for initiator.
"""
name_dict = {'node.session.auth.authmethod': 'CHAP'}
name_dict['node.session.auth.username'] = self.chap_user
name_dict['node.session.auth.password'] = self.chap_passwd
for name in name_dict.keys():
cmd = "iscsiadm --mode node --targetname %s " % self.target
cmd += "--op update --name %s --value %s" % (name, name_dict[name])
try:
utils.system(cmd)
except error.CmdError:
logging.error("Fail to set CHAP authentication for initiator")
def logout(self):
"""
Logout from target.
"""
if self.logged_in():
iscsi_logout(self.target)
def cleanup(self):
"""
Clean up env after iscsi used.
"""
self.logout()
iscsi_node_del(self.target)
if os.path.isfile("%s-%s" % (ISCSI_CONFIG_FILE, self.id)):
cmd = "mv %s-%s %s" % (ISCSI_CONFIG_FILE, self.id, ISCSI_CONFIG_FILE)
utils.system(cmd)
cmd = "service iscsid restart"
utils.system(cmd)
if self.export_flag:
self.delete_target()
[docs]class IscsiTGT(_IscsiComm):
"""
iscsi support TGT backend used in RHEL6.
"""
def __init__(self, params, root_dir):
"""
initialize TGT backend for iSCSI
:param params: parameters dict for TGT backend of iSCSI.
"""
super(IscsiTGT, self).__init__(params, root_dir)
[docs] def get_target_id(self):
"""
Get target id from image name. Only works for emulated iscsi device
"""
cmd = "tgtadm --lld iscsi --mode target --op show"
target_info = utils.system_output(cmd)
target_id = ""
for line in re.split("\n", target_info):
if re.findall("Target\s+(\d+)", line):
target_id = re.findall("Target\s+(\d+)", line)[0]
if re.findall("Backing store path:\s+(/+.+)", line):
if self.emulated_image in line:
break
else:
target_id = ""
return target_id
[docs] def get_chap_accounts(self):
"""
Get all CHAP authentication accounts
"""
cmd = "tgtadm --lld iscsi --op show --mode account"
all_accounts = utils.system_output(cmd)
if all_accounts:
all_accounts = map(str.strip, all_accounts.splitlines()[1:])
return all_accounts
[docs] def add_chap_account(self):
"""
Add CHAP authentication account
"""
try:
cmd = "tgtadm --lld iscsi --op new --mode account"
cmd += " --user %s" % self.chap_user
cmd += " --password %s" % self.chap_passwd
utils.system(cmd)
except error.CmdError, err:
logging.error("Fail to add account: %s", err)
# Check the new add account exist
if self.chap_user not in self.get_chap_accounts():
logging.error("Can't find account %s" % self.chap_user)
[docs] def delete_chap_account(self):
"""
Delete the CHAP authentication account
"""
if self.chap_user in self.get_chap_accounts():
cmd = "tgtadm --lld iscsi --op delete --mode account"
cmd += " --user %s" % self.chap_user
utils.system(cmd)
[docs] def get_target_account_info(self):
"""
Get the target account information
"""
cmd = "tgtadm --lld iscsi --mode target --op show"
target_info = utils.system_output(cmd)
pattern = r"Target\s+\d:\s+%s" % self.target
pattern += ".*Account information:\s(.*)ACL information"
try:
target_account = re.findall(pattern, target_info,
re.S)[0].strip().splitlines()
except IndexError:
target_account = []
return map(str.strip, target_account)
[docs] def set_chap_auth_target(self):
"""
Set CHAP authentication on a target, it will require authentication
before an initiator is allowed to log in and access devices.
"""
if self.chap_user not in self.get_chap_accounts():
self.add_chap_account()
if self.chap_user in self.get_target_account_info():
logging.debug("Target %s already has account %s", self.target,
self.chap_user)
else:
cmd = "tgtadm --lld iscsi --op bind --mode account"
cmd += " --tid %s --user %s" % (self.emulated_id, self.chap_user)
utils.system(cmd)
[docs] def export_target(self):
"""
Export target in localhost for emulated iscsi
"""
selinux_mode = None
if not os.path.isfile(self.emulated_image):
utils.system(self.create_cmd)
else:
emulated_image_size = os.path.getsize(self.emulated_image) / 1024
if emulated_image_size != self.emulated_expect_size:
# No need to remvoe, rebuild is fine
utils.system(self.create_cmd)
cmd = "tgtadm --lld iscsi --mode target --op show"
try:
output = utils.system_output(cmd)
except error.CmdError:
utils.system("service tgtd restart")
output = utils.system_output(cmd)
if not re.findall("%s$" % self.target, output, re.M):
logging.debug("Need to export target in host")
# Set selinux to permissive mode to make sure iscsi target
# export successfully
if utils_selinux.is_enforcing():
selinux_mode = utils_selinux.get_status()
utils_selinux.set_status("permissive")
output = utils.system_output(cmd)
used_id = re.findall("Target\s+(\d+)", output)
emulated_id = 1
while str(emulated_id) in used_id:
emulated_id += 1
self.emulated_id = str(emulated_id)
cmd = "tgtadm --mode target --op new --tid %s" % self.emulated_id
cmd += " --lld iscsi --targetname %s" % self.target
utils.system(cmd)
cmd = "tgtadm --lld iscsi --op bind --mode target "
cmd += "--tid %s -I ALL" % self.emulated_id
utils.system(cmd)
else:
target_strs = re.findall("Target\s+(\d+):\s+%s$" %
self.target, output, re.M)
self.emulated_id = target_strs[0].split(':')[0].split()[-1]
cmd = "tgtadm --lld iscsi --mode target --op show"
try:
output = utils.system_output(cmd)
except error.CmdError: # In case service stopped
utils.system("service tgtd restart")
output = utils.system_output(cmd)
# Create a LUN with emulated image
if re.findall(self.emulated_image, output, re.M):
# Exist already
logging.debug("Exported image already exists.")
self.export_flag = True
else:
tgt_str = re.search(r'.*(Target\s+\d+:\s+%s\s*.*)$' % self.target,
output, re.DOTALL)
if tgt_str:
luns = len(re.findall("\s+LUN:\s(\d+)",
tgt_str.group(1), re.M))
else:
luns = len(re.findall("\s+LUN:\s(\d+)", output, re.M))
cmd = "tgtadm --mode logicalunit --op new "
cmd += "--tid %s --lld iscsi " % self.emulated_id
cmd += "--lun %s " % luns
cmd += "--backing-store %s" % self.emulated_image
utils.system(cmd)
self.export_flag = True
self.luns = luns
# Restore selinux
if selinux_mode is not None:
utils_selinux.set_status(selinux_mode)
if self.chap_flag:
# Set CHAP authentication on the exported target
self.set_chap_auth_target()
# Set CHAP authentication for initiator to login target
if self.portal_visible():
self.set_chap_auth_initiator()
[docs] def delete_target(self):
"""
Delete target from host.
"""
cmd = "tgtadm --lld iscsi --mode target --op show"
output = utils.system_output(cmd)
if re.findall("%s$" % self.target, output, re.M):
if self.emulated_id:
cmd = "tgtadm --lld iscsi --mode target --op delete "
cmd += "--tid %s" % self.emulated_id
utils.system(cmd)
if self.restart_tgtd:
cmd = "service tgtd restart"
utils.system(cmd)
[docs]class IscsiLIO(_IscsiComm):
"""
iscsi support class for LIO backend used in RHEL7.
"""
def __init__(self, params, root_dir):
"""
initialize LIO backend for iSCSI
:param params: parameters dict for LIO backend of iSCSI
"""
super(IscsiLIO, self).__init__(params, root_dir)
[docs] def get_target_id(self):
"""
Get target id from image name.
"""
cmd = "targetcli ls /iscsi 1"
target_info = utils.system_output(cmd)
target = None
for line in re.split("\n", target_info)[1:]:
if re.findall("o-\s\S+\s[\.]+\s\[TPGs:\s\d\]$", line):
# eg: iqn.2015-05.com.example:iscsi.disk
try:
target = re.findall("iqn[\.]\S+:\S+", line)[0]
except IndexError:
logging.info("No found target in %s", line)
continue
else:
continue
cmd = "targetcli ls /iscsi/%s/tpg1/luns" % target
luns_info = utils.system_output(cmd)
for lun_line in re.split("\n", luns_info):
if re.findall("o-\slun\d+", lun_line):
if self.emulated_image in lun_line:
break
else:
target = None
return target
[docs] def set_chap_acls_target(self):
"""
set CHAP(acls) authentication on a target.
it will require authentication
before an initiator is allowed to log in and access devices.
notice:
Individual ACL entries override common TPG Authentication,
which can be set by set_chap_auth_target().
"""
# Enable ACL nodes
acls_cmd = "targetcli /iscsi/%s/tpg1/ " % self.target
attr_cmd = "set attribute generate_node_acls=0"
utils.system(acls_cmd + attr_cmd)
# Create user and allow access
acls_cmd = ("targetcli /iscsi/%s/tpg1/acls/ create %s:client"
% (self.target, self.target.split(":")[0]))
output = utils.system_output(acls_cmd)
if "Created Node ACL" not in output:
raise error.TestFail("Failed to create ACL. (%s)" % output)
comm_cmd = ("targetcli /iscsi/%s/tpg1/acls/%s:client/"
% (self.target, self.target.split(":")[0]))
# Set userid
userid_cmd = "%s set auth userid=%s" % (comm_cmd, self.chap_user)
output = utils.system_output(userid_cmd)
if self.chap_user not in output:
raise error.TestFail("Failed to set user. (%s)" % output)
# Set password
passwd_cmd = "%s set auth password=%s" % (comm_cmd, self.chap_passwd)
output = utils.system_output(passwd_cmd)
if self.chap_passwd not in output:
raise error.TestFail("Failed to set password. (%s)" % output)
# Save configuration
utils.system("targetcli / saveconfig")
[docs] def set_chap_auth_target(self):
"""
set up authentication information for every single initiator,
which provides the capability to define common login information
for all Endpoints in a TPG
"""
auth_cmd = "targetcli /iscsi/%s/tpg1/ " % self.target
attr_cmd = ("set attribute %s %s %s" %
("demo_mode_write_protect=0",
"generate_node_acls=1",
"cache_dynamic_acls=1"))
utils.system(auth_cmd + attr_cmd)
# Set userid
userid_cmd = "%s set auth userid=%s" % (auth_cmd, self.chap_user)
output = utils.system_output(userid_cmd)
if self.chap_user not in output:
raise error.TestFail("Failed to set user. (%s)" % output)
# Set password
passwd_cmd = "%s set auth password=%s" % (auth_cmd, self.chap_passwd)
output = utils.system_output(passwd_cmd)
if self.chap_passwd not in output:
raise error.TestFail("Failed to set password. (%s)" % output)
# Save configuration
utils.system("targetcli / saveconfig")
[docs] def export_target(self):
"""
Export target in localhost for emulated iscsi
"""
selinux_mode = None
# create image disk
if not os.path.isfile(self.emulated_image):
utils.system(self.create_cmd)
else:
emulated_image_size = os.path.getsize(self.emulated_image) / 1024
if emulated_image_size != self.emulated_expect_size:
# No need to remvoe, rebuild is fine
utils.system(self.create_cmd)
# confirm if the target exists and create iSCSI target
cmd = "targetcli ls /iscsi 1"
output = utils.system_output(cmd)
if not re.findall("%s$" % self.target, output, re.M):
logging.debug("Need to export target in host")
# Set selinux to permissive mode to make sure
# iscsi target export successfully
if utils_selinux.is_enforcing():
selinux_mode = utils_selinux.get_status()
utils_selinux.set_status("permissive")
# In fact, We've got two options here
#
# 1) Create a block backstore that usually provides the best
# performance. We can use a block device like /dev/sdb or
# a logical volume previously created,
# (lvcreate -name lv_iscsi -size 1G vg)
# 2) Create a fileio backstore,
# which enables the local file system cache.
#
# This class Only works for emulated iscsi device,
# So fileio backstore is enough and safe.
# Create a fileio backstore
device_cmd = ("targetcli /backstores/fileio/ create %s %s" %
(self.device, self.emulated_image))
output = utils.system_output(device_cmd)
if "Created fileio" not in output:
raise error.TestFail("Failed to create fileio %s. (%s)"
% (self.device, output))
# Create an IQN with a target named target_name
target_cmd = "targetcli /iscsi/ create %s" % self.target
output = utils.system_output(target_cmd)
if "Created target" not in output:
raise error.TestFail("Failed to create target %s. (%s)"
% (self.target, output))
check_portal = "targetcli /iscsi/%s/tpg1/portals ls" % self.target
portal_info = utils.system_output(check_portal)
if "0.0.0.0:3260" not in portal_info:
# Create portal
# 0.0.0.0 means binding to INADDR_ANY
# and using default IP port 3260
portal_cmd = ("targetcli /iscsi/%s/tpg1/portals/ create %s"
% (self.target, "0.0.0.0"))
output = utils.system_output(portal_cmd)
if "Created network portal" not in output:
raise error.TestFail("Failed to create portal. (%s)"
% output)
if ("ipv6" == utils_net.IPAddress(self.portal_ip).version and
self.portal_ip not in portal_info):
# Ipv6 portal address can't be created by default,
# create ipv6 portal if needed.
portal_cmd = ("targetcli /iscsi/%s/tpg1/portals/ create %s"
% (self.target, self.portal_ip))
output = utils.system_output(portal_cmd)
if "Created network portal" not in output:
raise error.TestFail("Failed to create portal. (%s)"
% output)
# Create lun
lun_cmd = "targetcli /iscsi/%s/tpg1/luns/ " % self.target
dev_cmd = "create /backstores/fileio/%s" % self.device
output = utils.system_output(lun_cmd + dev_cmd)
luns = re.findall(r"Created LUN (\d+).", output)
if not luns:
raise error.TestFail("Failed to create lun. (%s)" % output)
self.luns = luns[0]
# Set firewall if it's enabled
output = utils.system_output("firewall-cmd --state",
ignore_status=True)
if re.findall("^running", output, re.M):
# firewall is running
utils.system("firewall-cmd --permanent --add-port=3260/tcp")
utils.system("firewall-cmd --reload")
# Restore selinux
if selinux_mode is not None:
utils_selinux.set_status(selinux_mode)
self.export_flag = True
else:
logging.info("Target %s has already existed!" % self.target)
if self.chap_flag:
# Set CHAP authentication on the exported target
self.set_chap_auth_target()
# Set CHAP authentication for initiator to login target
if self.portal_visible():
self.set_chap_auth_initiator()
else:
# To enable that so-called "demo mode" TPG operation,
# disable all authentication for the corresponding Endpoint.
# which means grant access to all initiators,
# so that they can access all LUNs in the TPG
# without further authentication.
auth_cmd = "targetcli /iscsi/%s/tpg1/ " % self.target
attr_cmd = ("set attribute %s %s %s %s" %
("authentication=0",
"demo_mode_write_protect=0",
"generate_node_acls=1",
"cache_dynamic_acls=1"))
output = utils.system_output(auth_cmd + attr_cmd)
logging.info("Define access rights: %s" % output)
# Save configuration
utils.system("targetcli / saveconfig")
[docs] def delete_target(self):
"""
Delete target from host.
"""
# Delete block
if self.device is not None:
cmd = "targetcli /backstores/fileio ls"
output = utils.system_output(cmd)
if re.findall("%s" % self.device, output, re.M):
dev_del = ("targetcli /backstores/fileio/ delete %s"
% self.device)
utils.system(dev_del)
# Delete IQN
cmd = "targetcli ls /iscsi 1"
output = utils.system_output(cmd)
if re.findall("%s" % self.target, output, re.M):
del_cmd = "targetcli /iscsi delete %s" % self.target
utils.system(del_cmd)
# Save deleted configuration to avoid restoring
cmd = "targetcli / saveconfig"
utils.system(cmd)
[docs]class Iscsi(object):
"""
Basic iSCSI support class,
which will handle the emulated iscsi export and
access to both real iscsi and emulated iscsi device.
The class support different kinds of iSCSI backend (TGT and LIO),
and return ISCSI instance.
"""
@staticmethod
[docs] def create_iSCSI(params, root_dir="/tmp"):
iscsi_instance = None
try:
os_dep.command("iscsiadm")
os_dep.command("tgtadm")
iscsi_instance = IscsiTGT(params, root_dir)
except ValueError:
try:
os_dep.command("iscsiadm")
os_dep.command("targetcli")
iscsi_instance = IscsiLIO(params, root_dir)
except ValueError:
pass
return iscsi_instance