Source code for virttest.iscsi

"""
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