diff --git a/.gitignore b/.gitignore index e69de29..593a6e1 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/targetcli-1.99.2.gitb03ec79.tar.gz diff --git a/0001-Remove-ads-from-cli-welcome-msg.-Mention-help-is-ava.patch b/0001-Remove-ads-from-cli-welcome-msg.-Mention-help-is-ava.patch new file mode 100644 index 0000000..ee0691b --- /dev/null +++ b/0001-Remove-ads-from-cli-welcome-msg.-Mention-help-is-ava.patch @@ -0,0 +1,29 @@ +From 786d5b84d653c019c391fa07e483947156a03e3d Mon Sep 17 00:00:00 2001 +From: Andy Grover +Date: Sat, 30 Jul 2011 16:02:43 -0700 +Subject: [PATCH 1/6] Remove ads from cli welcome msg. Mention help is available. + +Signed-off-by: Andy Grover +--- + scripts/targetcli | 4 ++-- + 1 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/scripts/targetcli b/scripts/targetcli +index 8af0613..738f02c 100755 +--- a/scripts/targetcli ++++ b/scripts/targetcli +@@ -58,9 +58,9 @@ def main(): + shell = TargetCLI('~/.targetcli') + shell.con.epy_write(''' + Welcome to the B{targetcli} shell:: +- Copyright (c) 2011 by RisingTide Systems LLC. ++ Copyright (c) 2011 by RisingTide Systems LLC.\n ++ For help on commands, type 'help'. + +- Visit us at U{http://www.risingtidesystems.com}. + ''') + shell.con.display('') + if not is_root: +-- +1.7.1 + diff --git a/0002-bundle-lio-utils.patch b/0002-bundle-lio-utils.patch new file mode 100644 index 0000000..4e497b5 --- /dev/null +++ b/0002-bundle-lio-utils.patch @@ -0,0 +1,3992 @@ +From c55d9206e4f783d25ceb574ba07c40a83daf00a3 Mon Sep 17 00:00:00 2001 +From: Andy Grover +Date: Thu, 18 Aug 2011 11:19:35 -0700 +Subject: [PATCH 2/6] bundle lio-utils + +lio-utils is used just for saving state. It should be going away +shortly, so just bundle it for now. + +Signed-off-by: Andy Grover +--- + targetcli/lio_dump.py | 265 +++++++++ + targetcli/lio_node.py | 1349 ++++++++++++++++++++++++++++++++++++++++++++++ + targetcli/tcm_dump.py | 366 +++++++++++++ + targetcli/tcm_fabric.py | 532 ++++++++++++++++++ + targetcli/tcm_fileio.py | 83 +++ + targetcli/tcm_iblock.py | 86 +++ + targetcli/tcm_loop.py | 242 +++++++++ + targetcli/tcm_node.py | 737 +++++++++++++++++++++++++ + targetcli/tcm_pscsi.py | 184 +++++++ + targetcli/tcm_ramdisk.py | 53 ++ + 10 files changed, 3897 insertions(+), 0 deletions(-) + create mode 100644 targetcli/lio_dump.py + create mode 100644 targetcli/lio_node.py + create mode 100644 targetcli/tcm_dump.py + create mode 100644 targetcli/tcm_fabric.py + create mode 100644 targetcli/tcm_fileio.py + create mode 100644 targetcli/tcm_iblock.py + create mode 100644 targetcli/tcm_loop.py + create mode 100644 targetcli/tcm_node.py + create mode 100644 targetcli/tcm_pscsi.py + create mode 100644 targetcli/tcm_ramdisk.py + +diff --git a/targetcli/lio_dump.py b/targetcli/lio_dump.py +new file mode 100644 +index 0000000..81c5104 +--- /dev/null ++++ b/targetcli/lio_dump.py +@@ -0,0 +1,265 @@ ++import os, sys ++import subprocess as sub ++import string ++import re ++import datetime, time ++from optparse import OptionParser ++ ++lio_root = "/sys/kernel/config/target/iscsi" ++ ++def lio_target_configfs_dump(option, opt_str, value, parser): ++ ++ if not os.path.isdir(lio_root): ++ print "Unable to access lio_root: " + lio_root ++ sys.exit(1) ++ ++ iqn_root = os.listdir(lio_root) ++ ++ # This will load up iscsi_target_mod.ko ++ print "mkdir " + lio_root ++ ++ print "#### iSCSI Discovery authentication information" ++ auth_dir = lio_root + "/discovery_auth" ++ if os.path.isdir(auth_dir) == True: ++ for auth in os.listdir(auth_dir): ++ if auth == "authenticate_target": ++ continue ++ ++ auth_file = auth_dir + "/" + auth ++ p = os.open(auth_file, 0) ++ value = os.read(p, 256) ++ ret = value.isspace() ++ if ret: ++ os.close(p) ++ continue ++ print "echo -n " + value.rstrip() + " > " + auth_file ++ os.close(p) ++ ++ iqn_root = os.listdir(lio_root) ++ ++ # Loop through LIO-Target IQN list ++ for iqn in iqn_root: ++ if iqn == "lio_version": ++ continue ++ if iqn == "discovery_auth": ++ continue ++ ++ # Loop through LIO-Target IQN+TPGT list ++ tpg_root = os.listdir(lio_root + "/" + iqn); ++ for tpgt_tmp in tpg_root: ++ if tpgt_tmp == "fabric_statistics": ++ continue ++ ++ tpgt_tmp2 = tpgt_tmp.split('_') ++ tpgt = tpgt_tmp2[1] ++ ++ print "#### Network portals for iSCSI Target Portal Group" ++ np_root = os.listdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/np") ++ for np in np_root: ++ print "mkdir -p " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/np/" + np ++ ++ print "#### iSCSI Target Ports" ++ lun_root = os.listdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun") ++ for lun_tmp in lun_root: ++ lun_tmp2 = lun_tmp.split('_') ++ lun = lun_tmp2[1] ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ print "mkdir -p " + lun_dir ++ ++ port_root = os.listdir(lun_dir) ++ for port in port_root: ++ if port == "alua_tg_pt_gp": ++ continue ++ if port == "alua_tg_pt_offline": ++ continue ++ if port == "alua_tg_pt_status": ++ continue ++ if port == "alua_tg_pt_write_md": ++ continue ++ ++ if not os.path.islink(lun_dir + "/" + port): ++ continue ++ ++ port_link = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun + "/" + port ++ sourcelink = os.readlink(port_link) ++ sourcelink2 = os.path.join(os.path.dirname(port_link), sourcelink) ++ print "ln -s " + sourcelink2 + " " + port_link ++ ++ # Dump ALUA Target Port Group ++ tg_pt_gp_file = lun_dir + "/alua_tg_pt_gp" ++ p = os.open(tg_pt_gp_file, 0) ++ try: ++ value = os.read(p, 512) ++ except: ++ os.close(p) ++ continue ++ os.close(p) ++ if value: ++ tg_pt_gp_tmp = value.split('\n') ++ tg_pt_gp_out = tg_pt_gp_tmp[0] ++ off = tg_pt_gp_out.index('Alias: ') ++ off += 7 # Skip over "Alias: " ++ tg_pt_gp_name = tg_pt_gp_out[off:] ++ # Only need to dump if LIO-Target Port is NOT partof ++ # the 'default_tg_pt_gp' ++ if not re.search(tg_pt_gp_name, 'default_tg_pt_gp'): ++ print "#### ALUA Target Port Group" ++ print "echo " + tg_pt_gp_name + " > " + tg_pt_gp_file ++ ++ print "lio_node --aluasecmd " + iqn + " " + tpgt + " " + lun ++ ++ # Dump values of iscsi/iqn/tpgt/attrib/ ++ print "#### Attributes for iSCSI Target Portal Group" ++ attrib_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib/" ++ attrib_root = os.listdir(attrib_dir) ++ for attrib in attrib_root: ++ attrib_file = attrib_dir + attrib ++ p = os.open(attrib_file, 0) ++ value = os.read(p, 16) ++ print "echo " + value.rstrip() + " > " + attrib_file ++ os.close(p) ++ ++ # Dump values for iscsi/iqn/tpgt/param ++ print "#### Parameters for iSCSI Target Portal Group" ++ param_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/param/" ++ param_root = os.listdir(param_dir) ++ for param in param_root: ++ param_file = param_dir + param ++ p = os.open(param_file, 0) ++ value = os.read(p, 256) ++ print "echo \"" + value.rstrip() + "\" > " + param_file ++ os.close(p) ++ ++ # Dump iSCSI Initiator Node ACLs from iscsi/iqn/tpgt/acls ++ print "#### iSCSI Initiator ACLs for iSCSI Target Portal Group" ++ nacl_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" ++ nacl_root = os.listdir(nacl_dir) ++ for nacl in nacl_root: ++ print "mkdir -p " + nacl_dir + nacl ++ tcq_depth_file = nacl_dir + nacl + "/cmdsn_depth" ++ p = os.open(tcq_depth_file, 0) ++ value = os.read(p, 8) ++ print "echo " + value.rstrip() + " > " + tcq_depth_file ++ os.close(p) ++ ++ # Dump iSCSI Initiator ACL authentication info from iscsi/iqn/tpgt/acls/$INITIATOR/auth ++ print "#### iSCSI Initiator ACL authentication information" ++ auth_dir = nacl_dir + nacl + "/auth" ++ for auth in os.listdir(auth_dir): ++ if auth == "authenticate_target": ++ continue ++ auth_file = auth_dir + "/" + auth ++ p = os.open(auth_file, 0) ++ value = os.read(p, 256) ++ ret = value.isspace() ++ if ret: ++ os.close(p) ++ continue ++ print "echo -n " + value.rstrip() + " > " + auth_file ++ os.close(p) ++ ++ # Dump iSCSI Initiator ACL TPG attributes from iscsi/iqn/tpgt/acls/$INITIATOR/attrib ++ print "#### iSCSI Initiator ACL TPG attributes" ++ nacl_attrib_dir = nacl_dir + nacl + "/attrib" ++ for nacl_attrib in os.listdir(nacl_attrib_dir): ++ nacl_attrib_file = nacl_attrib_dir + "/" + nacl_attrib ++ p = os.open(nacl_attrib_file, 0) ++ value = os.read(p, 8) ++ print "echo " + value.rstrip() + " > " + nacl_attrib_file ++ os.close(p) ++ ++ # Dump iSCSI Initiator LUN ACLs from iscsi/iqn/tpgt/acls/$INITIATOR/lun ++ print "#### iSCSI Initiator LUN ACLs for iSCSI Target Portal Group" ++ lun_acl_dir = nacl_dir + nacl ++ for lun_acl in os.listdir(lun_acl_dir): ++ ret = re.search('lun_', lun_acl) ++ if not ret: ++ continue ++ lun_link_dir = nacl_dir + nacl + "/" + lun_acl ++ print "mkdir -p " + lun_link_dir ++ ++ for lun_acl_link in os.listdir(lun_link_dir): ++ if lun_acl_link == "write_protect": ++ p = os.open(lun_link_dir + "/write_protect", 0) ++ value = os.read(p, 4) ++ print "echo " + value.rstrip() + " > " + lun_link_dir + "/write_protect" ++ os.close(p) ++ continue ++ ++ if not os.path.islink(lun_link_dir + "/" + lun_acl_link): ++ continue ++ ++ sourcelink = os.readlink(lun_link_dir + "/" + lun_acl_link) ++ sourcelink2 = os.path.join(os.path.dirname(lun_link_dir + "/" + lun_acl_link), sourcelink) ++ print "ln -s " + sourcelink2 + " " + lun_link_dir + "/" + lun_acl_link ++ ++ # Dump value of iscsi/iqn/tpgt/enable ++ print "#### Trigger to enable iSCSI Target Portal Group" ++ enable_file = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/enable" ++ p = os.open(enable_file, 0) ++ value = os.read(p, 1) ++ print "echo " + value.rstrip() + " > " + enable_file ++ os.close(p) ++ ++# lio_target_del_iqn(None, None, iqn, None) ++ ++ return ++ ++def lio_backup_to_file(option, opt_str, value, parser): ++ now = str(value) ++ ++ if not os.path.isdir(lio_root): ++ print "Unable to access lio_root: " + lio_root ++ sys.exit(1) ++ ++ backup_dir = "/etc/target/backup" ++ if not os.path.isdir(backup_dir): ++ op = "mkdir " + backup_dir ++ ret = os.system(op) ++ if ret: ++ print "Unable to open backup_dir" ++ sys.exit(1) ++ ++ op = "lio_dump --stdout" ++ p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout ++ if not p: ++ print "Unable to dump LIO-Target/ConfigFS running state" ++ sys.exit(1) ++ ++ print "Making backup of LIO-Target/ConfigFS with timestamp: " + now ++ backup_file = backup_dir + "/lio_backup-" + now + ".sh" ++ if os.path.isfile(backup_file): ++ print "LIO-Target backup_file: " + backup_file + "already exists, exiting" ++ p.close() ++ sys.exit(1) ++ ++ back = open(backup_file, 'w') ++ ++ line = p.readline() ++ while line: ++ print >>back, line.rstrip() ++ line = p.readline() ++ ++ p.close() ++ back.close() ++ return backup_file ++ ++def main(): ++ ++ parser = OptionParser() ++ parser.add_option("--s","--stdout", action="callback", callback=lio_target_configfs_dump, nargs=0, ++ help="Dump running LIO-Target/ConfigFS syntax to STDOUT") ++ parser.add_option("--t", "--tofile", action="callback", callback=lio_backup_to_file, nargs=1, ++ type="string", dest="DATE_TIME", help="Backup running LIO-Target/ConfigFS syntax to /etc/target/backup/lio_backup-.sh") ++ ++ (options, args) = parser.parse_args() ++ if len(sys.argv) == 1: ++ parser.print_help() ++ sys.exit(0) ++ elif not re.search('--', sys.argv[1]): ++ print "Unknown CLI option: " + sys.argv[1] ++ sys.exit(1) ++ ++if __name__ == "__main__": ++ main() +diff --git a/targetcli/lio_node.py b/targetcli/lio_node.py +new file mode 100644 +index 0000000..de2c1e3 +--- /dev/null ++++ b/targetcli/lio_node.py +@@ -0,0 +1,1349 @@ ++import os, sys ++import subprocess as sub ++import string ++import re ++from optparse import OptionParser ++ ++tcm_root = "/sys/kernel/config/target/core" ++lio_root = "/sys/kernel/config/target/iscsi" ++alua_secondary_md_dir = "/var/target/alua/iSCSI/" ++ ++def lio_err(msg): ++ print msg ++ sys.exit(1) ++ ++def lio_alua_check_secondary_md(iqn, tpgt): ++ alua_sec_md_path = alua_secondary_md_dir + iqn + "+" + tpgt + "/" ++ if os.path.isdir(alua_sec_md_path) == False: ++ mkdir_op = "mkdir -p " + alua_sec_md_path ++ ret = os.system(mkdir_op) ++ if ret: ++ lio_err("Unable to create secondary ALUA MD directory: " + alua_sec_md_path) ++ ++ return ++ ++def lio_alua_delete_secondary_md(iqn, tpgt): ++ alua_sec_md_path = alua_secondary_md_dir + iqn + "+" + tpgt + "/" ++ if os.path.isdir(alua_sec_md_path) == False: ++ return ++ ++ rm_op = "rm -rf " + alua_sec_md_path ++ ret = os.system(rm_op) ++ if ret: ++ lio_err("Unable to remove secondary ALUA MD directory: " + alua_sec_md_path) ++ ++ return ++ ++def lio_alua_delete_secondary_md_port(iqn, tpgt, lun): ++ ++ alua_sec_md_file = alua_secondary_md_dir + iqn + "+" + tpgt + "/lun_" + lun ++ if os.path.isfile(alua_sec_md_file) == False: ++ return ++ ++ rm_op = "rm -rf "+ alua_sec_md_file ++ ret = os.system(rm_op) ++ if ret: ++ lio_err("Unable to delete ALUA secondary metadata file: " + alua_sec_md_file) ++ ++ return ++ ++def lio_alua_set_secondary_write_md(iqn, tpgt, lun): ++ alua_write_md_file = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun + "/alua_tg_pt_write_md" ++ if os.path.isfile(alua_write_md_file) == False: ++ return ++ ++ p = open(alua_write_md_file, 'w') ++ if not p: ++ lio_err("Unable to open: " + alua_write_md_file) ++ ++ ret = p.write("1") ++ if ret: ++ lio_err("Unable to enable writeable ALUA secondary metadata for " + alua_write_md_file) ++ ++ p.close() ++ return ++ ++def lio_alua_process_secondary_md(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ ++ alua_sec_md_file = alua_secondary_md_dir + iqn + "+" + tpgt + "/lun_" + lun ++ if os.path.isfile(alua_sec_md_file) == False: ++ # Use --aluasecmd as a chance to make sure the directory for this ++ # LIO-Target endpoint (iqn+tpgt) exists.. ++ lio_alua_check_secondary_md(iqn, tpgt) ++ lio_alua_set_secondary_write_md(iqn, tpgt, lun) ++ lio_err("Unable to locate ALUA secondary metadata file: " + alua_sec_md_file) ++ return ++ ++# print "Using alua_sec_md_file: " + alua_sec_md_file ++ alua_sec_cfs_path = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++# print "Using alua_sec_cfs_path: " + alua_sec_cfs_path ++ ++ p = open(alua_sec_md_file, 'rU') ++ if not p: ++ print "Unable to process ALUA secondary metadata for: " + alua_sec_md_file ++ ++ line = p.readline() ++ while line: ++ buf = line.rstrip() ++ ++ if re.search('alua_tg_pt_offline=', buf): ++ alua_tg_pt_offline = buf[19:] ++# print "Extracted alua_tg_pt_offline: " + alua_tg_pt_offline ++ cfs = open(alua_sec_cfs_path + "/alua_tg_pt_offline", 'w') ++ if not cfs: ++ p.close() ++ lio_err("Unable to open " + alua_sec_cfs_path + "/alua_tg_pt_offline") ++ ++ ret = cfs.write(alua_tg_pt_offline) ++ cfs.close() ++ if ret: ++ p.close() ++ lio_err("Unable to write " + alua_sec_cfs_path + "/alua_tg_pt_offline") ++ ++ elif re.search('alua_tg_pt_status=', buf): ++ alua_tg_pt_status = buf[18:] ++# print "Extracted alua_tg_pt_status: " + alua_tg_pt_status ++ cfs = open(alua_sec_cfs_path + "/alua_tg_pt_status", 'w') ++ if not cfs: ++ p.close() ++ lio_err("Unable to open " + alua_sec_cfs_path + "/alua_tg_pt_status") ++ ++ ret = cfs.write(alua_tg_pt_status) ++ cfs.close() ++ if ret: ++ p.close() ++ lio_err("Unable to write " + alua_sec_cfs_path + "/alua_tg_pt_status") ++ ++ line = p.readline() ++ ++ p.close() ++ # Now enable the alua_tg_pt_write_md bit to allow for new updates ++ # to ALUA secondary metadata in struct file for this port ++ lio_alua_set_secondary_write_md(iqn, tpgt, lun) ++ return ++ ++def __lio_target_del_iqn(option, opt_str, value, parser, delete_tpg_md): ++ iqn = str(value); ++ iqn = iqn.lower(); ++ ++# Loop through LIO-Target IQN+TPGT list ++ tpg_root = os.listdir(lio_root + "/" + iqn); ++ for tpgt_tmp in tpg_root: ++ if tpgt_tmp == "fabric_statistics": ++ continue ++ ++ tpgt_tmp2 = tpgt_tmp.split('_') ++ tpgt = tpgt_tmp2[1] ++ ++ tpg_val = [iqn,tpgt] ++ if delete_tpg_md == 1: ++ lio_target_del_tpg(None, None, tpg_val, None) ++ else: ++ __lio_target_del_tpg(None, None, tpg_val, None, 0) ++ ++ rmdir_op = "rmdir " + lio_root + "/" + iqn ++# print "rmdir_op: " + rmdir_op ++ ret = os.system(rmdir_op) ++ if not ret: ++ print "Successfully released iSCSI Target Endpoint IQN: " + iqn ++ else: ++ lio_err("Unable to release iSCSI Target Endpoint IQN: " + iqn) ++ ++ return ++ ++def lio_target_del_iqn(option, opt_str, value, parser): ++ iqn = str(value); ++ iqn = iqn.lower(); ++ ++ iqn_dir = lio_root + "/" + iqn ++ if os.path.isdir(iqn_dir) == False: ++ lio_err("Unable to locate iSCSI Target IQN: " + iqn_dir) ++ ++ # Passing 1 for delete_tpg_md here means lio_target_del_tpg() ++ # with lio_alua_delete_secondary_md() will get called to delete ++ # all of the secondary ALUA directories for the LIO-Target endpoint ++ # when an explict --deliqn is called. ++ __lio_target_del_iqn(option, opt_str, value, parser, 1) ++ ++ return ++ ++def __lio_target_del_tpg(option, opt_str, value, parser, delete_tpg_md): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ # This will set TPG Status to INACTIVE force all of the iSCSI sessions for this ++ # tiqn+tpgt tuple to be released. ++ disable_op = "echo 0 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/enable" ++ ret = os.system(disable_op) ++ if ret: ++ print "Unable to disable TPG: " + iqn + " TPGT: " + tpgt ++ ++ np_root = os.listdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/np") ++ for np in np_root: ++ np_val = [iqn,tpgt,np] ++ ++ lio_target_del_np(None, None, np_val, None) ++ ++ nacl_root = os.listdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls") ++ for nacl in nacl_root: ++ nacl_val = [iqn,tpgt,nacl] ++ ++ lio_target_del_nodeacl(None, None, nacl_val, None) ++ ++ lun_root = os.listdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun") ++ for lun_tmp in lun_root: ++ lun_tmp2 = lun_tmp.split('_') ++ lun = lun_tmp2[1] ++ ++ lun_val = [iqn,tpgt,lun] ++ if delete_tpg_md == 1: ++ lio_target_del_port(None, None, lun_val, None) ++ else: ++ __lio_target_del_port(None, None, lun_val, None) ++ ++ ++ rmdir_op = "rmdir " + lio_root + "/" + iqn + "/tpgt_" + tpgt ++# print "rmdir_op: " + rmdir_op ++ ret = os.system(rmdir_op) ++ if not ret: ++ print "Successfully released iSCSI Target Portal Group: " + iqn + " TPGT: " + tpgt ++ else: ++ lio_err("Unable to release iSCSI Target Portal Group: " + iqn + " TPGT: " + tpgt) ++ ++ return ++ ++def lio_target_del_tpg(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ tpgt_file = lio_root + "/" + iqn + "/tpgt_" + tpgt ++ if os.path.isdir(tpgt_file) == False: ++ lio_err("iSCSI Target Port Group: " + tpgt_file + " does not exist") ++ ++ __lio_target_del_tpg(option, opt_str, value, parser, 1) ++ # Delete the ALUA secondary metadata directory on explict --deltpg or ++ # called from --deliqn ++ lio_alua_delete_secondary_md(iqn, tpgt) ++ return ++ ++def lio_target_add_np(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ np = str(value[2]); ++ ++ # Append default iSCSI Port is non is given. ++ if re.search(']', np)and not re.search(']:', np): ++ np = np + ":3260" ++ elif re.search(':\\Z', np): ++ np = np + "3260" ++ elif not re.search(':', np): ++ np = np + ":3260" ++ ++ # Extract the iSCSI port and make sure it is a valid u16 value ++ if re.search(']:', np): ++ off = np.index(']:') ++ off += 2 ++ port = int(np[off:]) ++ else: ++ off = np.index(':') ++ off += 1 ++ port = int(np[off:]) ++ ++ if port == 0 or port > 65535: ++ lio_err("Illegal port value: " + str(port) + " for iSCSI network portal") ++ ++ mkdir_op = "mkdir -p " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/np/" + np ++# print "mkdir_op: " + mkdir_op ++ ret = os.system(mkdir_op) ++ if not ret: ++ print "Successfully created network portal: " + np + " created " + iqn + " TPGT: " + tpgt ++ lio_alua_check_secondary_md(iqn, tpgt) ++ else: ++ lio_err("Unable to create network portal: " + np + " created " + iqn + " TPGT: " + tpgt) ++ ++ return ++ ++def lio_target_del_np(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ np = str(value[2]); ++ ++ rmdir_op = "rmdir " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/np/" + np ++# print "rmdir_op: " + rmdir_op ++ ret = os.system(rmdir_op) ++ if not ret: ++ print "Successfully released network portal: " + np + " created " + iqn + " TPGT: " + tpgt ++ else: ++ lio_err("Unable to release network portal: " + np + " created " + iqn + " TPGT: " + tpgt) ++ ++ return ++ ++def lio_target_add_port(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ port_name = str(value[3]); ++ tcm_obj = str(value[4]); ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ if os.path.isdir(lun_dir): ++ lio_err("iSCSI Target Logical Unit ConfigFS directory already exists") ++ ++ mkdir_op = "mkdir -p " + lun_dir ++# print "mkdir_op: " + mkdir_op ++ ret = os.system(mkdir_op) ++ if ret: ++ lio_err("Unable to create iSCSI Target Logical Unit ConfigFS directory") ++ ++ port_src = tcm_root + "/" + tcm_obj ++ port_dst = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun + "/" + port_name ++ link_op = "ln -s " + port_src + " " + port_dst ++# print "link_op: " + link_op ++ ret = os.system(link_op) ++ if not ret: ++ print "Successfully created iSCSI Target Logical Unit" ++ lio_alua_check_secondary_md(iqn, tpgt) ++ lio_alua_set_secondary_write_md(iqn, tpgt, lun) ++ else: ++ os.rmdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun) ++ lio_err("Unable to create iSCSI Target Logical Unit symlink") ++ ++ return ++ ++def lio_target_add_tpg(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ tpg_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt ++ if os.path.isdir(tpg_dir): ++ lio_err("iSCSI Target Portal Group directory already exists") ++ ++ mkdir_op = "mkdir -p " + tpg_dir ++ ret = os.system(mkdir_op) ++ if ret: ++ lio_err("Unable to create iSCSI Target Portal Group ConfigFS directory") ++ else: ++ print "Successfully created iSCSI Target Portal Group" ++ lio_alua_check_secondary_md(iqn, tpgt) ++ ++ return ++ ++def __lio_target_del_port(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ port_root = os.listdir(lun_dir) ++ ++ for port in port_root: ++ if port == "alua_tg_pt_gp": ++ continue ++ if port == "alua_tg_pt_offline": ++ continue ++ if port == "alua_tg_pt_status": ++ continue ++ if port == "alua_tg_pt_write_md": ++ continue ++ ++ if not os.path.islink(lun_dir + "/" + port): ++ continue ++ ++ unlink_op = "unlink " + lun_dir + "/" + port ++# print "del_portunlink_op: " + unlink_op ++ ret = os.system(unlink_op) ++ if ret: ++ lio_err("Unable to unlink iSCSI Target Logical Unit") ++ ++ rmdir_op= "rmdir " + lun_dir ++# print "del_port rmdir_op: " + rmdir_op ++ ret = os.system(rmdir_op); ++ if not ret: ++ print "Successfully deleted iSCSI Target Logical Unit" ++ else: ++ lio_err("Unable to rmdir iSCSI Target Logical Unit configfs directory") ++ ++ return ++ ++def lio_target_del_port(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ if os.path.isdir(lun_dir) == False: ++ lio_err("LIO-Target Port/LUN directory: " + lun_dir + " does not exist") ++ ++ __lio_target_del_port(option, opt_str, value, parser) ++ # Delete the ALUA secondary metadata file for this Port/LUN ++ # during an explict --dellun ++ lio_alua_delete_secondary_md_port(iqn, tpgt, lun) ++ ++ return ++ ++def lio_target_tpg_disableauth(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ enable_op = "echo 0 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib/authentication" ++ ret = os.system(enable_op) ++ if ret: ++ lio_err("Unable to disable iSCSI Authentication on iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully disabled iSCSI Authentication on iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_tpg_demomode(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ enable_op = "echo 1 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib/generate_node_acls" ++ ret = os.system(enable_op) ++ if ret: ++ lio_err("Unable to disable Initiator ACL mode (Enable DemoMode) on iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully disabled Initiator ACL mode (Enabled DemoMode) on iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_disable_lunwp(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ mapped_lun = str(value[3]); ++ ++ disable_op = "echo 0 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun + "/write_protect" ++ ret = os.system(disable_op) ++ if ret: ++ lio_err("Unable to disable WriteProtect for Mapped LUN: " + mapped_lun + " for " + initiator_iqn + " on iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully disabled WRITE PROTECT for Mapped LUN: " + mapped_lun + " for " + initiator_iqn + " on iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_enable_auth(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ enable_op = "echo 1 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib/authentication" ++ ret = os.system(enable_op) ++ if ret: ++ lio_err("Unable to enable iSCSI Authentication on iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully enabled iSCSI Authentication on iSCSI Target Portal Group: " + iqn + " " + tpgt ++ return ++ ++def lio_target_enable_lunwp(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ mapped_lun = str(value[3]); ++ ++ enable_op = "echo 1 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun + "/write_protect" ++ ret = os.system(enable_op) ++ if ret: ++ lio_err("Unable to enable WriteProtect for Mapped LUN: " + mapped_lun + " for " + initiator_iqn + " on iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully enabled WRITE PROTECT for Mapped LUN: " + mapped_lun + " for " + initiator_iqn + " on iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_enable_tpg(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ enable_op = "echo 1 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/enable" ++ ret = os.system(enable_op) ++ if ret: ++ lio_err("Unable to enable iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully enabled iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_disable_tpg(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ disable_op = "echo 0 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/enable" ++ ret = os.system(disable_op) ++ if ret: ++ lio_err("Unable to disable iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully disabled iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_enableaclmode(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ enable_op = "echo 0 > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib/generate_node_acls" ++ ret = os.system(enable_op) ++ if ret: ++ lio_err("Unable to enable Initiator ACL mode (Disabled DemoMode) on iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully enabled Initiator ACL mode (Disabled DemoMode) on iSCSI Target Portal Group: " + iqn + " " + tpgt ++ return ++ ++ ++def lio_target_add_lunacl(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ tpg_lun = str(value[3]); ++ mapped_lun = str(value[4]); ++ ++ mkdir_op = "mkdir -p " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun ++ ret = os.system(mkdir_op) ++ if ret: ++ lio_err("Unable to add iSCSI Initiator Mapped LUN: " + mapped_lun + " ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ addlunacl_op = "ln -s " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + tpg_lun + " " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun + "/lio_lun" ++ ++ ret = os.system(addlunacl_op) ++ if ret: ++ lio_err("Unable to add iSCSI Initiator Mapped LUN: " + mapped_lun + " ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully added iSCSI Initiator Mapped LUN: " + mapped_lun + " ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ lio_alua_check_secondary_md(iqn, tpgt) ++ ++ return ++ ++def lio_target_del_lunacl(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ mapped_lun = str(value[3]); ++ ++ lun_link_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun ++ for lun_acl_link in os.listdir(lun_link_dir): ++ if lun_acl_link == "write_protect": ++ continue ++ ++ if not os.path.islink(lun_link_dir + "/" + lun_acl_link): ++ continue; ++ ++ unlink_op = "unlink " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun + "/" + lun_acl_link ++# print "unlink_op: " + unlink_op ++ ret = os.system(unlink_op) ++ if ret: ++ lio_err("Unable to unlink iSCSI Initiator Mapped LUN: " + mapped_lun + " ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ dellunacl_op = "rmdir " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/lun_" + mapped_lun ++ ret = os.system(dellunacl_op) ++ if ret: ++ lio_err("Unable to delete iSCSI Initiator Mapped LUN: " + mapped_lun + " ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully deleted iSCSI Initiator Mapped LUN: " + mapped_lun + " ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_add_nodeacl(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ ++ addnodeacl_op = "mkdir -p " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn ++ ret = os.system(addnodeacl_op) ++ if ret: ++ lio_err("Unable to add iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully added iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ lio_alua_check_secondary_md(iqn, tpgt) ++ ++ return ++ ++def lio_target_del_nodeacl(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ ++ nacl_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn ++ lun_acl_root = os.listdir(nacl_dir) ++ for lun_acl in lun_acl_root: ++ ret = re.search('lun_', lun_acl) ++ if not ret: ++ continue ++ lun_delacl_val = [iqn,tpgt,initiator_iqn,lun_acl[4:]] ++ lio_target_del_lunacl(None, None, lun_delacl_val, None) ++ ++ delnodeacl_op = "rmdir " + nacl_dir ++ ret = os.system(delnodeacl_op) ++ if ret: ++ lio_err("Unable to delete iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully deleted iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_set_chap_auth(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ user = str(value[3]); ++ password = str(value[4]); ++ ++ auth_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/auth/" ++ ++ if not os.path.isdir(auth_dir): ++ lio_err("iSCSI Initiator ACL " + initiator_iqn + " does not exist for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ setuser_op = "echo -n " + user + " > " + auth_dir + "/userid" ++ ret = os.system(setuser_op) ++ if ret: ++ lio_err("Unable to set CHAP username for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ setpassword_op = "echo -n " + password + " > " + auth_dir + "/password" ++ ret = os.system(setpassword_op) ++ if ret: ++ lio_err("Unable to set CHAP password for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully set CHAP authentication for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_set_chap_mutual_auth(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ user_mutual = str(value[3]); ++ password_mutual = str(value[4]); ++ ++ auth_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/auth/" ++ ++ if not os.path.isdir(auth_dir): ++ lio_err("iSCSI Initiator ACL " + initiator_iqn + " does not exist for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ setuser_op = "echo -n " + user_mutual + " > " + auth_dir + "/userid_mutual" ++ ret = os.system(setuser_op) ++ if ret: ++ lio_err("Unable to set mutual CHAP username for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ setpassword_op = "echo -n " + password_mutual + " > " + auth_dir + "/password_mutual" ++ ret = os.system(setpassword_op) ++ if ret: ++ lio_err("Unable to set mutual CHAP password for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully set mutual CHAP authentication for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_set_chap_discovery_auth(option, opt_str, value, parser): ++ user = str(value[0]); ++ password = str(value[1]); ++ ++ auth_dir = lio_root + "/discovery_auth" ++ if not os.path.isdir(auth_dir): ++ lio_err("iSCSI Discovery Authentication directory " + auth_dir + " does not exist") ++ ++ setuser_op = "echo -n " + user + " > " + auth_dir + "/userid" ++ ret = os.system(setuser_op) ++ if ret: ++ lio_err("Unable to set CHAP username for iSCSI Discovery Authentication") ++ ++ setpassword_op = "echo -n " + password + " > " + auth_dir + "/password" ++ ret = os.system(setpassword_op) ++ if ret: ++ lio_err("Unable to set CHAP password for iSCSI Discovery Authentication") ++ else: ++ print "Successfully set CHAP authentication for iSCSI Discovery Authentication" ++ ++ return ++ ++def lio_target_set_chap_mutual_discovery_auth(option, opt_str, value, parser): ++ user_mutual = str(value[0]); ++ password_mutual = str(value[1]); ++ ++ auth_dir = lio_root + "/discovery_auth" ++ if not os.path.isdir(auth_dir): ++ lio_err("iSCSI Discovery Authentication directory " + auth_dir + " does not exist") ++ ++ setuser_op = "echo -n " + user_mutual + " > " + auth_dir + "/userid_mutual" ++ ret = os.system(setuser_op) ++ if ret: ++ lio_err("Unable to set mutual CHAP username for iSCSI Discovery Authentication") ++ ++ setpassword_op = "echo -n " + password_mutual + " > " + auth_dir + "/password_mutual" ++ ret = os.system(setpassword_op) ++ if ret: ++ lio_err("Unable to set mutual CHAP password for iSCSI Discovery Authentication") ++ else: ++ print "Successfully set mutual CHAP authentication for iSCSI Discovery Authentication" ++ ++ return ++ ++def lio_target_set_enforce_discovery_auth(option, opt_str, value, parser): ++ value = str(value); ++ ++ da_attr = lio_root + "/discovery_auth/enforce_discovery_auth" ++ if not os.path.isfile(da_attr): ++ lio_err("iSCSI Discovery Authentication directory does not exist") ++ ++ da_op = "echo " + value + " > " + da_attr; ++ ret = os.system(da_op) ++ if ret: ++ lio_err("Unable to set da_attr: " + da_attr) ++ ++ if value == "1": ++ print "Successfully enabled iSCSI Discovery Authentication enforcement" ++ else: ++ print "Successfully disabled iSCSI Discovery Authentication enforcement" ++ ++ return ++ ++ ++def lio_target_set_node_tcq(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ depth = str(value[3]); ++ ++ setnodetcq_op = "echo " + depth + " > " + lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/cmdsn_depth" ++ ret = os.system(setnodetcq_op) ++ if ret: ++ lio_err("Unable to set TCQ: " + depth + " for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ else: ++ print "Successfully set TCQ: " + depth + " for iSCSI Initaitor ACL " + initiator_iqn + " for iSCSI Target Portal Group: " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_alua_set_tgptgp(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ tg_pt_gp_name = str(value[3]) ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ if not os.path.isdir(lun_dir): ++ lio_err("LIO-Target Port/LUN: " + lun + " does not exist on: " + iqn + " " + tpgt) ++ ++ set_tp_pt_gp_op = "echo " + tg_pt_gp_name + " > " + lun_dir + "/alua_tg_pt_gp" ++ ret = os.system(set_tp_pt_gp_op) ++ if ret: ++ lio_err("Unable to set ALUA Target Port Group: " + tg_pt_gp_name + " for LUN: " + lun + " on " + iqn + " " + tpgt) ++ else: ++ print "Successfully set ALUA Target Port Group: " + tg_pt_gp_name + " for LUN: " + lun + " on " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_alua_set_tgpt_offline(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ if not os.path.isdir(lun_dir): ++ lio_err("LIO-Target Port/LUN: " + lun + " does not exist on: " + iqn + " " + tpgt) ++ ++ set_tg_pt_gp_offline_op = "echo 1 > " + lun_dir + "/alua_tg_pt_offline" ++ ret = os.system(set_tg_pt_gp_offline_op) ++ if ret: ++ lio_err("Unable to set ALUA secondary state OFFLINE bit for LUN: " + lun + " on " + iqn + " " + tpgt) ++ else: ++ print "Successfully set ALUA secondary state OFFLINE for LUN: " + lun + " on " + iqn + " " + tpgt ++ ++ return ++ ++def lio_target_alua_clear_tgpt_offline(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ if not os.path.isdir(lun_dir): ++ lio_err("LIO-Target Port/LUN: " + lun + " does not exist on: " + iqn + " " + tpgt) ++ ++ set_tg_pt_gp_offline_op = "echo 0 > " + lun_dir + "/alua_tg_pt_offline" ++ ret = os.system(set_tg_pt_gp_offline_op) ++ if ret: ++ lio_err("Unable to clear ALUA secondary state OFFLINE for LUN: " + lun + " on " + iqn + " " + tpgt) ++ else: ++ print "Successfully cleared ALUA secondary state OFFLINE for LUN: " + lun + " on " + iqn + " " + tpgt ++ return ++ ++def lio_target_show_chap_auth(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ ++ auth_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/auth/" ++ ++ if not os.path.isdir(auth_dir): ++ lio_err("iSCSI Initiator ACL " + initiator_iqn + " does not exist for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ for auth in os.listdir(auth_dir): ++ p = os.open(auth_dir + "/" + auth, 0) ++ value = os.read(p, 256) ++ print auth + ": " + value.rstrip() ++ os.close(p) ++ ++ return ++ ++def lio_target_show_chap_discovery_auth(option, opt_str, value, parser): ++ ++ auth_dir = lio_root + "/discovery_auth" ++ if not os.path.isdir(auth_dir): ++ lio_err("iSCSI Discovery Authentication directory " + auth_dir + " does not exist") ++ ++ for auth in os.listdir(auth_dir): ++ p = os.open(auth_dir + "/" + auth, 0) ++ value = os.read(p, 256) ++ print auth + ": " + value.rstrip() ++ os.close(p) ++ ++ return ++ ++def lio_target_show_node_tcq(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]); ++ initiator_iqn = initiator_iqn.lower(); ++ ++ nacl = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn ++ if not os.path.isdir(nacl): ++ lio_err("iSCSI Initiator ACL: " + initiator_iqn + " does not exist for iSCSI Target Portal Group: " + iqn + " " + tpgt) ++ ++ tcq_depth_file = nacl + "/cmdsn_depth" ++ p = os.open(tcq_depth_file, 0) ++ value = os.read(p, 8) ++ print value.rstrip() ++ os.close(p) ++ ++ return ++ ++def lio_target_alua_show_tgptgp(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ lun = str(value[2]); ++ ++ lun_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ if not os.path.isdir(lun_dir): ++ lio_err("LIO-Target Port/LUN: " + lun + " does not exist on: " + iqn + " " + tpgt) ++ ++ show_tp_pt_gp_op = "cat " + lun_dir + "/alua_tg_pt_gp" ++ ret = os.system(show_tp_pt_gp_op) ++ if ret: ++ lio_err("Unable to show ALUA Target Port Group: " + tg_pt_gp_name + " for LUN: " + lun + " on " + iqn + " " + tpgt) ++ ++ return ++ ++def lio_target_list_endpoints(option, opt_str, value, parser): ++ ++ iqn_root = os.listdir(lio_root) ++ ++ for iqn in iqn_root: ++ if iqn == "lio_version": ++ continue ++ if iqn == "discovery_auth": ++ continue ++ ++ print "\------> " + iqn ++ ++ tpg_root = lio_root + "/" + iqn ++ for tpg in os.listdir(tpg_root): ++ if tpg == "fabric_statistics": ++ continue ++ ++ p = os.open(tpg_root + "/" + tpg + "/param/TargetAlias", 0) ++ value = os.read(p, 256) ++ print " \-------> " + tpg + " TargetAlias: " + value.rstrip() ++ os.close(p) ++ ++ print " TPG Status:", ++ p = os.open(tpg_root + "/" + tpg + "/enable", 0) ++ value = os.read(p, 8) ++ enable_bit = value.rstrip(); ++ if enable_bit == '1': ++ print "ENABLED" ++ else: ++ print "DISABLED" ++ os.close(p) ++ ++ print " TPG Network Portals:" ++ np_root = tpg_root + "/" + tpg + "/np" ++ for np in os.listdir(np_root): ++ print " \-------> " + np ++ ++ print " TPG Logical Units:" ++ lun_root = tpg_root + "/" + tpg + "/lun" ++ for lun in os.listdir(lun_root): ++ port_dir = lun_root + "/" + lun ++ for port in os.listdir(port_dir): ++ if port == "alua_tg_pt_gp": ++ continue ++ if port == "alua_tg_pt_offline": ++ continue ++ if port == "alua_tg_pt_status": ++ continue ++ if port == "alua_tg_pt_write_md": ++ continue ++ if port == "statistics": ++ continue ++ ++ port_link = port_dir + "/" + port ++ if not os.path.islink(port_link): ++ continue ++ ++ sourcelink = os.readlink(port_link) ++ # Skip over ../../../../../ in sourcelink" ++ print " \-------> " + lun + "/" + port + " -> " + sourcelink[18:] ++ ++ ++ return ++ ++def lio_target_list_lunacls(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ iqn_root = os.listdir(lio_root) ++ ++ nacl_root_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls" ++ nacl_root = os.listdir(nacl_root_dir) ++ for nacl in nacl_root: ++ print "\------> InitiatorName ACL: " + nacl ++ print " Logical Unit ACLs: " ++ lun_root_dir = nacl_root_dir + "/" + nacl ++ lun_root = os.listdir(lun_root_dir) ++ for lun in lun_root: ++ ret = re.search('lun_', lun) ++ if not ret: ++ continue ++ ++ wp_attrib = lun_root_dir + "/" + lun + "/write_protect" ++ wp_file = open(wp_attrib); ++ line = wp_file.readline() ++ wp_bit = line.rstrip() ++ if wp_bit == '1': ++ wp_info = "ENABLED" ++ else: ++ wp_info = "DISABLED" ++ ++ lun_link_dir = lun_root_dir + "/" + lun ++ for lun_link in os.listdir(lun_link_dir): ++ if lun_link == "write_protect": ++ continue ++ if lun_link == "statistics": ++ continue ++ ++ if not os.path.islink(lun_link_dir + "/" + lun_link): ++ continue ++ ++ sourcelink = os.readlink(lun_link_dir + "/" + lun_link) ++ # Skip over ../../../../../../ in sourcelink" ++ print " \-------> " + lun + " -> " + sourcelink[21:] ++ print " \-------> Write Protect for " + lun + ": " + wp_info ++ ++ return ++ ++def lio_target_list_nodeacls(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ iqn_root = os.listdir(lio_root) ++ ++ nacl_root_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls" ++ nacl_root = os.listdir(nacl_root_dir) ++ for nacl in nacl_root: ++ print "\------> InitiatorName: " + nacl ++ info_attrib = nacl_root_dir + "/" + nacl + "/info" ++ file = open(info_attrib, "r") ++ line = file.readline() ++ ret = re.search('No active iSCSI Session for Initiator Endpoint', line) ++ if ret: ++ print " No active iSCSI Session for Initiator Endpoint" ++ else: ++ line = file.readline() ++ while line: ++ print " " + line.rstrip() ++ line = file.readline() ++ ++ file.close() ++ ++ return ++ ++def lio_target_list_nps(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ np_root = os.listdir(lio_root + "/" + iqn + "/tpgt_" + tpgt + "/np") ++ for np in np_root: ++ print np ++ ++ return ++ ++def lio_target_list_targetnames(option, opt_str, value, parser): ++ ++ iqn_root = os.listdir(lio_root) ++ ++ # Loop through LIO-Target IQN list ++ for iqn in iqn_root: ++ if iqn == "lio_version": ++ continue ++ if iqn == "discovery_auth": ++ continue ++ ++ print iqn ++ ++ return ++ ++def lio_target_list_node_attr(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]) ++ ++ attr_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/attrib/" ++ if os.path.isdir(attr_dir) == False: ++ lio_err("Unable to locate node attr_dir: " + attr_dir) ++ ++ for attr in os.listdir(attr_dir): ++ p = open(attr_dir + "/" + attr, 'rU') ++ if not p: ++ lio_err("Unable to open attr: " + attr_dir + "/" + attr) ++ ++ val = p.read() ++ p.close() ++ ++ print attr + "=" + val.rstrip() ++ ++ return ++ ++def lio_target_set_node_attr(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]) ++ attr = str(value[3]) ++ val = str(value[4]) ++ ++ attr_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/attrib/" ++ if os.path.isdir(attr_dir) == False: ++ lio_err("Unable to locate node attr_dir: " + attr_dir) ++ ++ p = open(attr_dir + "/" + attr, 'w') ++ if not p: ++ lio_err("Unable to open node attr: " + attr_dir + "/" + attr) ++ ++ ret = p.write(val) ++ if ret: ++ lio_err("Unable to set node attr: " + attr_dir + "/" + attr) ++ ++ p.close() ++ print "Successfully set Initiator Node attribute: " + attr + " to: " + val ++ ++ return ++ ++def lio_target_list_node_param(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ initiator_iqn = str(value[2]) ++ ++ param_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" + initiator_iqn + "/param" ++ if os.path.isdir(param_dir) == False: ++ lio_err("Unable to locate node param_dir: " + param_dir) ++ ++ for param in os.listdir(param_dir): ++ p = open(param_dir + "/" + param, 'rU') ++ if not p: ++ lio_err("Unable to open attr: " + param_dir + "/" + param) ++ ++ val = p.read() ++ p.close() ++ ++ print param + "=" + val.rstrip() ++ ++ return ++ ++ ++def lio_target_list_tpg_attr(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ attr_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib" ++ if os.path.isdir(attr_dir) == False: ++ lio_err("Unable to locate tpg attr_dir: " + attr_dir) ++ ++ for attr in os.listdir(attr_dir): ++ p = open(attr_dir + "/" + attr, 'rU') ++ if not p: ++ lio_err("Unable to open attr: " + attr_dir + "/" + attr) ++ ++ val = p.read() ++ p.close() ++ ++ print attr + "=" + val.rstrip() ++ ++ return ++ ++def lio_target_set_tpg_attr(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ attr = str(value[2]); ++ val = str(value[3]); ++ ++ attr_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib" ++ if os.path.isdir(attr_dir) == False: ++ lio_err("Unable to locate tpg attr_dir: " + attr_dir) ++ ++ p = open(attr_dir + "/" + attr, 'w') ++ if not p: ++ lio_err("Unable to open tpg attr: " + attr_dir + "/" + attr) ++ ++ ret = p.write(val) ++ if ret: ++ lio_err("Unable to set tpg attr: " + attr_dir + "/" + attr) ++ ++ p.close() ++ print "Successfully set TPG attribute: " + attr + " to: " + val ++ ++ return ++ ++def lio_target_list_tpg_param(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ ++ param_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/param" ++ if os.path.isdir(param_dir) == False: ++ lio_err("Unable to locate tpg param dir: " + param_dir) ++ ++ for param in os.listdir(param_dir): ++ p = open(param_dir + "/" + param, 'rU') ++ if not p: ++ lio_err("Unable to open param: " + param_dir + "/" + param) ++ ++ val = p.read() ++ p.close() ++ ++ print param + "=" + val.rstrip() ++ ++ return ++ ++def lio_target_set_tpg_param(option, opt_str, value, parser): ++ iqn = str(value[0]); ++ iqn = iqn.lower(); ++ tpgt = str(value[1]); ++ param = str(value[2]); ++ val = str(value[3]); ++ ++ param_dir = lio_root + "/" + iqn + "/tpgt_" + tpgt + "/param" ++ if os.path.isdir(param_dir) == False: ++ lio_err("Unable to locate tpg param dir: " + param_dir) ++ ++ p = open(param_dir + "/" + param, 'w') ++ if not p: ++ lio_err("Unable to open tpg attr: " + param_dir + "/" + param) ++ ++ val = val + " " ++ ret = p.write(val) ++ if ret: ++ lio_err("Unable to write tpg attr: " + param_dir + "/" + param) ++ ++ p.close() ++ print "Successfully set TPG parameter: " + param + " to: " + val ++ ++ return ++ ++def lio_target_unload(option, opt_str, value, parser): ++ ++ if not os.path.isdir(lio_root): ++ lio_err("Unable to access lio_root: " + lio_root) ++ ++ iqn_root = os.listdir(lio_root) ++ ++ # Loop through LIO-Target IQN list ++ for iqn in iqn_root: ++ if iqn == "lio_version": ++ continue ++ if iqn == "discovery_auth": ++ continue ++ ++ # Loop through LIO-Target IQN+TPGT list ++ tpg_root = os.listdir(lio_root + "/" + iqn); ++ for tpgt_tmp in tpg_root: ++ if tpgt_tmp == "fabric_statistics": ++ continue ++ ++ tpgt_tmp2 = tpgt_tmp.split('_') ++ tpgt = tpgt_tmp2[1] ++ ++ tpg_val = [iqn,tpgt] ++ __lio_target_del_tpg(None, None, tpg_val, None, 0) ++ ++ __lio_target_del_iqn(None, None, iqn, None, 0) ++ ++ rmdir_op = "rmdir " + lio_root ++ ret = os.system(rmdir_op) ++ if ret: ++ print "Unable to release lio_root: " + lio_root ++ ++ rmmod_op = "rmmod iscsi_target_mod" ++ ret = os.system(rmmod_op) ++ if ret: ++ print "Unable to unload iscsi_target_mod" ++ ++ return ++ ++def lio_target_version(option, opt_str, value, parser): ++ ++ os.system("cat /sys/kernel/config/target/iscsi/lio_version") ++ return ++ ++def main(): ++ ++ parser = OptionParser() ++ parser.add_option("--addlunacl", action="callback", callback=lio_target_add_lunacl, nargs=5, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN TPG_LUN MAPPED_LUN", help="Add iSCSI Initiator LUN ACL to LIO-Target Portal Group LUN") ++ parser.add_option("--addnodeacl", action="callback", callback=lio_target_add_nodeacl, nargs=3, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN", help="Add iSCSI Initiator ACL to LIO-Target Portal Group") ++ parser.add_option("--addnp", action="callback", callback=lio_target_add_np, nargs=3, ++ type="string", dest="TARGET_IQN TPGT IP:PORT", help="Add LIO-Target IPv6 or IPv4 network portal") ++ parser.add_option("--addlun", action="callback", callback=lio_target_add_port, nargs=5, ++ type="string", dest="TARGET_IQN TPGT LUN PORT_ALIAS TCM_HBA/DEV ", help="Create LIO-Target Logical Unit") ++ parser.add_option("--addtpg", action="callback", callback=lio_target_add_tpg, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Create LIO-Target portal group") ++ parser.add_option("--aluasecmd", action="callback", callback=lio_alua_process_secondary_md, nargs=3, ++ type="string", dest="TARGET_IQN TPGT LUN", help="Process ALUA secondary metadata for Port/LUN"); ++ parser.add_option("--cleartgptoff","--clearaluaoff", action="callback", callback=lio_target_alua_clear_tgpt_offline, nargs=3, ++ type="string", dest="TARGET_IQN TPGT LUN", help="Clear ALUA Target Port Secondary State OFFLINE") ++ parser.add_option("--dellunacl", action="callback", callback=lio_target_del_lunacl, nargs=4, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN MAPPED_LUN", help="Delete iSCSI Initiator LUN ACL from LIO-Target Portal Group LUN") ++ parser.add_option("--delnodeacl", action="callback", callback=lio_target_del_nodeacl, nargs=3, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN", help="Delete iSCSI Initiator ACL from LIO-Target Portal Group") ++ parser.add_option("--delnp", action="callback", callback=lio_target_del_np, nargs=3, ++ type="string", dest="TARGET_IQN TPGT IP:PORT", help="Delete LIO-Target IPv6 or IPv4 network portal") ++ parser.add_option("--deliqn", action="callback", callback=lio_target_del_iqn, nargs=1, ++ type="string", dest="TARGET_IQN", help="Delete LIO-Target IQN Endpoint") ++ parser.add_option("--dellun", action="callback", callback=lio_target_del_port, nargs=3, ++ type="string", dest="TARGET_IQN TPGT LUN", help="Delete LIO-Target Logical Unit") ++ parser.add_option("--deltpg", action="callback", callback=lio_target_del_tpg, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Delete LIO-Target Portal Group") ++ parser.add_option("--demomode", "--permissive", action="callback", callback=lio_target_tpg_demomode, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Disable all iSCSI Initiator ACL requirements (enable DemoMode) for LIO-Target Portal Group (Disabled by default)") ++ parser.add_option("--disableauth", action="callback", callback=lio_target_tpg_disableauth, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Disable iSCSI Authentication for LIO-Target Portal Group (Enabled by default)") ++ parser.add_option("--disablelunwp", action="callback", callback=lio_target_disable_lunwp, nargs=4, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN MAPPED_LUN", help="Clear Write Protect bit for iSCSI Initiator LUN ACL") ++ parser.add_option("--disabletpg", action="callback", callback=lio_target_disable_tpg, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Disable LIO-Target Portal Group") ++ parser.add_option("--enableaclmode", action="callback", callback=lio_target_enableaclmode, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Enable iSCSI Initiator ACL requirement mode for LIO-Target Portal Group (Enabled by default)") ++ parser.add_option("--enableauth", action="callback", callback=lio_target_enable_auth, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Enable iSCSI Authentication for LIO-Target Portal Group (Enabled by default)") ++ parser.add_option("--enablelunwp", action="callback", callback=lio_target_enable_lunwp, nargs=4, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN MAPPED_LUN", help="Set Write Protect bit for iSCSI Initiator LUN ACL") ++ parser.add_option("--enabletpg", action="callback", callback=lio_target_enable_tpg, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="Enable LIO-Target Portal Group") ++ parser.add_option("--listendpoints", action="callback", callback=lio_target_list_endpoints, nargs=0, ++ help="List iSCSI Target Endpoints") ++ parser.add_option("--listlunacls", action="callback", callback=lio_target_list_lunacls, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="List iSCSI Initiator LUN ACLs for LIO-Target Portal Group") ++ parser.add_option("--listnodeacls", action="callback", callback=lio_target_list_nodeacls, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="List iSCSI Initiator ACLs for LIO-Target Portal Group") ++ parser.add_option("--listnodeattr", action="callback", callback=lio_target_list_node_attr, nargs=3, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN", help="List iSCSI Initiator ACL attributes for LIO-Target Portal Group") ++ parser.add_option("--listnodeparam", action="callback", callback=lio_target_list_node_param, nargs=3, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN", help="List iSCSI Initiator ACL RFC-3720 parameters for LIO-Target Portal Group") ++ parser.add_option("--listnps", action="callback", callback=lio_target_list_nps, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="List LIO-Target Portal Group Network Portals") ++ parser.add_option("--listtargetnames", action="callback", callback=lio_target_list_targetnames, nargs=0, ++ help="List iSCSI Target Names") ++ parser.add_option("--listtpgattr", action="callback", callback=lio_target_list_tpg_attr, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="List LIO-Target Portal Group attributes") ++ parser.add_option("--listtpgparam", action="callback", callback=lio_target_list_tpg_param, nargs=2, ++ type="string", dest="TARGET_IQN TPGT", help="List LIO-Target Portal Group RFC-3720 parameters") ++ parser.add_option("--setchapauth", action="callback", callback=lio_target_set_chap_auth, nargs=5, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN USER PASS", help="Set CHAP authentication information for iSCSI Initiator Node ACL"); ++ parser.add_option("--setchapmutualauth", action="callback", callback=lio_target_set_chap_mutual_auth, nargs=5, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN USER_IN PASS_IN", help="Set CHAP mutual authentication information for iSCSI Initiator Node ACL"); ++ parser.add_option("--setchapdiscenforce", action="callback", callback=lio_target_set_enforce_discovery_auth, nargs=1, ++ type="string", dest="Enforce=1, NoEnforcement=0", help="Set CHAP authentication enforcement for iSCSI Discovery Sessions"); ++ parser.add_option("--setchapdiscauth", action="callback", callback=lio_target_set_chap_discovery_auth, nargs=2, ++ type="string", dest="USER PASS", help="Set CHAP authentication information for iSCSI Discovery Authentication") ++ parser.add_option("--setchapdiscmutualauth", action="callback", callback=lio_target_set_chap_mutual_discovery_auth, nargs=2, ++ type="string", dest="USER PASS", help="Set CHAP mutual authentication information for iSCSI Discovery Authentication") ++ parser.add_option("--setnodeattr", action="callback", callback=lio_target_set_node_attr, nargs=5, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN ", help="Set iSCSI Initiator ACL Attribute") ++ parser.add_option("--setnodetcq", action="callback", callback=lio_target_set_node_tcq, nargs=4, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN DEPTH", help="Set iSCSI Initiator ACL TCQ Depth for LIO-Target Portal Group") ++ parser.add_option("--settpgattr", action="callback", callback=lio_target_set_tpg_attr, nargs=4, ++ type="string", dest="TARGET_IQN TPGT ", help="Set LIO-Target Port Group Attribute") ++ parser.add_option("--settpgparam", action="callback", callback=lio_target_set_tpg_param, nargs=4, ++ type="string", dest="TARGET_IQN TPGT ", help="Set LIO-Target Port Group RFC-3720 parameter") ++ parser.add_option("--settgptgp","--setaluatpg", action="callback", callback=lio_target_alua_set_tgptgp, nargs=4, ++ type="string", dest="TARGET_IQN TPGT LUN TG_PT_GP_NAME", help="Set ALUA Target Port Group for LIO-Target Port/LUN") ++ parser.add_option("--settgptoff","--setaluaoff", action="callback", callback=lio_target_alua_set_tgpt_offline, nargs=3, ++ type="string", dest="TARGET_IQN TPGT LUN", help="Set ALUA Target Port Secondary State OFFLINE") ++ parser.add_option("--showchapauth", action="callback", callback=lio_target_show_chap_auth, nargs=3, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN", help="Show CHAP authentication information for iSCSI Initiator Node ACL"); ++ parser.add_option("--showchapdiscauth", action="callback", callback=lio_target_show_chap_discovery_auth, nargs=0, ++ help="Show CHAP authentication information for iSCSI Discovery portal"); ++ parser.add_option("--shownodetcq", action="callback", callback=lio_target_show_node_tcq, nargs=3, ++ type="string", dest="TARGET_IQN TPGT INITIATOR_IQN", help="Show iSCSI Initiator ACL TCQ Depth for LIO-Target Portal Group") ++ parser.add_option("--showtgptgp", action="callback", callback=lio_target_alua_show_tgptgp, nargs=3, ++ type="string", dest="TARGET_IQN TPGT LUN", help="Show ALUA Target Port Group for LIO-Target Port/LUN") ++ parser.add_option("--unload", action="callback", callback=lio_target_unload, nargs=0, ++ help="Unload LIO-Target") ++ parser.add_option("--version", action="callback", callback=lio_target_version, nargs=0, ++ help="Display LIO-Target version information") ++ ++ (options, args) = parser.parse_args() ++ if len(sys.argv) == 1: ++ parser.print_help() ++ sys.exit(0) ++ elif not re.search('--', sys.argv[1]): ++ lio_err("Unknown CLI option: " + sys.argv[1]) ++ ++if __name__ == "__main__": ++ main() +diff --git a/targetcli/tcm_dump.py b/targetcli/tcm_dump.py +new file mode 100644 +index 0000000..bf80632 +--- /dev/null ++++ b/targetcli/tcm_dump.py +@@ -0,0 +1,366 @@ ++import os, sys, shutil ++import subprocess as sub ++from subprocess import Popen, PIPE ++import string ++import re ++import datetime, time ++from optparse import OptionParser ++ ++import tcm_node ++import tcm_pscsi ++import tcm_iblock ++import tcm_ramdisk ++import tcm_fileio ++ ++import lio_dump ++import tcm_fabric ++ ++tcm_root = "/sys/kernel/config/target/core" ++ ++def tcm_dump_hba_devices(): ++ pass ++ ++def tcm_dump_configfs(option, opt_str, value, parser): ++ ++ if not os.path.isdir(tcm_root): ++ print "Unable to access tcm_root: " + tcm_root ++ sys.exit(1) ++ ++ print "modprobe target_core_mod" ++ ++ # Loop through ALUA Logical Unit Groups ++ # Note that the 'default_lu_gp' is automatically created when ++ # target_core_mod is loaded. ++ print "#### ALUA Logical Unit Groups" ++ for lu_gp in os.listdir(tcm_root + "/alua/lu_gps"): ++ if lu_gp == "default_lu_gp": ++ continue ++ ++ print "mkdir -p " + tcm_root + "/alua/lu_gps/" + lu_gp ++ lu_gp_id_file = tcm_root + "/alua/lu_gps/" + lu_gp + "/lu_gp_id" ++ p = os.open(lu_gp_id_file, 0) ++ value = os.read(p, 8) ++ os.close(p) ++ if not value: ++ continue ++ print "echo " + value.rstrip() + " > " + lu_gp_id_file ++ ++ # Loop through HBA list ++ for f in os.listdir(tcm_root): ++ if f == "alua": ++ continue; ++ ++# print "mkdir -p " + tcm_root + "/" + f ++ ++ dev_root = tcm_root + "/" + f + "/" ++ for g in os.listdir(dev_root): ++ if g == "hba_info" or g == "hba_mode": ++ continue; ++ ++ # Dump device aka storage object ++ print "#### Parameters for TCM subsystem plugin storage object reference" ++ ++ # Generate subsystem dependent configfs ops for association to ++ # an target_core_mod storage object. ++ result = re.search('pscsi_', f) ++ if result: ++ dev = dev_root + g ++ params = tcm_pscsi.pscsi_get_params(dev) ++ if not params: ++ continue ++ print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ result = re.search('iblock_', f) ++ if result: ++ dev = dev_root + g ++ params = tcm_iblock.iblock_get_params(dev) ++ if not params: ++ continue ++ print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ result = re.search('rd_dr_', f) ++ if result: ++ dev = dev_root + g ++ params = tcm_ramdisk.rd_get_params(dev) ++ if not params: ++ continue ++ print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ result = re.search('rd_mcp_', f) ++ if result: ++ dev = dev_root + g ++ params = tcm_ramdisk.rd_get_params(dev) ++ if not params: ++ continue ++ print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ result = re.search('fileio_', f) ++ if result: ++ dev = dev_root + g ++ params = tcm_fileio.fd_get_params(dev) ++ if not params: ++ continue ++ print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ ++ # Dump T10 VP Unit Serial for all non Target_Core_Mod/pSCSI objects ++ result = re.search('pscsi_', f) ++ if not result: ++ unit_serial_file = dev_root + g + "/wwn/vpd_unit_serial" ++ p = os.open(unit_serial_file, 0) ++ value = os.read(p, 512) ++ off = value.index('Number: ') ++ off += 8 # Skip over "Number: " ++ unit_serial = value[off:] ++ # Note that this will handle read, parse and set any PR APTPL metadata ++ print "tcm_node --setunitserialwithmd " + f + "/" + g + " " + unit_serial.rstrip() ++ os.close(p) ++ ++ # Dump device object alias ++ alias_file = dev_root + g + "/alias" ++ p = open(alias_file, 'r') ++ value = p.read(512) ++ value = value.rstrip() ++ p.close() ++ if value: ++ print "echo -n \"" + value + "\" > " + dev_root + g + "/alias" ++ ++ # Dump ALUA Logical Unit Group ++ lu_gp_file = dev_root + g + "/alua_lu_gp" ++ p = os.open(lu_gp_file, 0) ++ value = os.read(p, 512) ++ os.close(p) ++ if value: ++ lu_gp_tmp = value.split('\n') ++ lu_gp_out = lu_gp_tmp[0] ++ off = lu_gp_out.index('Alias: ') ++ off += 7 # Skip over "Alias: " ++ lu_gp_name = lu_gp_out[off:] ++ # Only need to dump if storage object is NOT part of ++ # the 'default_lu_gp' ++ if not re.search(lu_gp_name, 'default_lu_gp'): ++ print "echo " + lu_gp_name + " > " + lu_gp_file ++ ++ # Loop through ALUA Target Port Groups ++ if os.path.isdir(dev_root + g + "/alua/") == True: ++ print "#### ALUA Target Port Groups" ++ for tg_pt_gp in os.listdir(dev_root + g + "/alua/"): ++ tg_pt_gp_id_file = dev_root + g + "/alua/" + tg_pt_gp + "/tg_pt_gp_id" ++ p = os.open(tg_pt_gp_id_file, 0) ++ value = os.read(p, 8) ++ os.close(p) ++ if not value: ++ continue ++ print "tcm_node --addaluatpgwithmd " + f + "/" + g + " " + tg_pt_gp + " " + value.rstrip() ++ # Dump the ALUA types ++ tg_pt_gp_type_file = dev_root + g + "/alua/" + tg_pt_gp + "/alua_access_type" ++ p = os.open(tg_pt_gp_type_file, 0) ++ value = os.read(p, 32) ++ os.close(p) ++ if value: ++ value = value.rstrip() ++ alua_type = 0 ++ ++ if re.search('Implict and Explict', value): ++ alua_type = 3 ++ elif re.search('Explict', value): ++ alua_type = 2 ++ elif re.search('Implict', value): ++ alua_type = 1 ++ ++ print "echo " + str(alua_type) + " > " + tg_pt_gp_type_file ++ ++ # Dump the preferred bit ++ tg_pt_gp_pref_file = dev_root + g + "/alua/" + tg_pt_gp + "/preferred" ++ p = os.open(tg_pt_gp_pref_file, 0) ++ value = os.read(p, 8) ++ os.close(p) ++ if value: ++ print "echo " + value.rstrip() + " > " + tg_pt_gp_pref_file ++ # Dump the Active/NonOptimized Delay ++ tg_pt_gp_nonop_delay_file = dev_root + g + "/alua/" + tg_pt_gp + "/nonop_delay_msecs" ++ p = os.open(tg_pt_gp_nonop_delay_file, 0) ++ value = os.read(p, 8) ++ os.close(p) ++ if value: ++ print "echo " + value.rstrip() + " > " + tg_pt_gp_nonop_delay_file ++ # Dump the Transition Delay ++ tg_pt_gp_trans_delay_file = dev_root + g + "/alua/" + tg_pt_gp + "/trans_delay_msecs" ++ p = os.open(tg_pt_gp_trans_delay_file, 0) ++ value = os.read(p, 8) ++ os.close(p) ++ if value: ++ print "echo " + value.rstrip() + " > " + tg_pt_gp_trans_delay_file ++ ++ # Dump device attributes ++ print "#### Attributes for " + dev_root + g ++ dev_attrib_root = dev_root + g + "/attrib/" ++ for h in os.listdir(dev_attrib_root): ++ # The hw_* prefixed attributes are RO ++ if h == "hw_queue_depth": ++ continue ++ if h == "hw_max_sectors": ++ continue ++ if h == "hw_block_size": ++ continue ++ # Do not change block-size for target_core_mod/pSCSI ++ if h == "block_size": ++ result = re.search('pscsi_', f) ++ if result: ++ continue ++ ++ attrib_file = dev_attrib_root + h ++ p = os.open(attrib_file, 0) ++ value = os.read(p, 8) ++ print "echo " + value.rstrip() + " > " + attrib_file ++ os.close(p) ++ ++ # Dump snapshot attributes ++ snap_attrib_root = dev_root + g + "/snap/" ++ if (os.path.isdir(snap_attrib_root) == False): ++ continue ++ ++ snap_enabled = 0 ++ enabled_attr_file = snap_attrib_root + "enabled" ++ p = open(enabled_attr_file, 'rU') ++ if not p: ++ continue ++ value = p.read() ++ enabled = value.rstrip() ++ p.close() ++ if enabled != "1": ++ continue ++ ++ snap_enabled = 1 ++ print "#### Snapshot Attributes for " + dev_root + g ++ for s in os.listdir(snap_attrib_root): ++ if s == "pid": ++ continue ++ if s == "usage": ++ continue ++ if s == "enabled": ++ continue ++ ++ attrib_file = snap_attrib_root + s ++ p = open(attrib_file, 'rU') ++ value = p.read() ++ p.close() ++ attr_val = value.rstrip() ++ print "echo " + attr_val + " > " + attrib_file ++ ++ if snap_enabled == 1: ++ print "tcm_node --lvsnapstart " + f + "/" + g ++ ++def tcm_backup_to_file(option, opt_str, value, parser): ++ datetime = str(value) ++ ++ if not os.path.isdir(tcm_root): ++ print "Unable to access tcm_root: " + tcm_root ++ sys.exit(1) ++ ++ backup_dir = "/etc/target/backup" ++ if not os.path.isdir(backup_dir): ++ op = "mkdir " + backup_dir ++ ret = os.system(op) ++ if ret: ++ print "Unable to open backup_dir" ++ sys.exit(1) ++ ++ op = "tcm_dump --stdout" ++ p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout ++ if not p: ++ print "Unable to dump Target_Core_Mod/ConfigFS running state" ++ sys.exit(0) ++ ++ print "Making backup of Target_Core_Mod/ConfigFS with timestamp: " + datetime ++ backup_file = backup_dir + "/tcm_backup-" + datetime + ".sh" ++ if os.path.isfile(backup_file): ++ print "Target_Core_Mod backup_file: " + backup_file + "already exists, exiting" ++ p.close() ++ sys.exit(1) ++ ++ back = open(backup_file, 'w') ++ ++ line = p.readline() ++ while line: ++ print >>back, line.rstrip() ++ line = p.readline() ++ ++ p.close() ++ back.close() ++ return backup_file ++ ++def tcm_full_backup(option, opt_str, value, parser): ++ overwrite = str(value) ++ ++ now = datetime.datetime.now() ++ tmp = str(now) ++ tmp2 = tmp.split(' ') ++ timestamp = tmp2[0] + "_" + tmp2[1] ++ ++ tcm_file = "/etc/target/tcm_start.sh" ++ lio_file = "/etc/target/lio_start.sh" ++ lio_active = 0 ++ ++ ret = tcm_fabric.fabric_backup_to_file_all(timestamp) ++ if ret: ++ print "Unable to backup tcm_fabric.modules" ++ ++ if os.path.isdir("/sys/kernel/config/target/iscsi"): ++ lio_file_new = lio_dump.lio_backup_to_file(None, None, timestamp, None) ++ if not lio_file_new: ++ sys.exit(1) ++ lio_active = 1 ++ print "Generated LIO-Target config: " + lio_file_new ++ ++ ++ tcm_file_new = tcm_backup_to_file(None, None, timestamp, None) ++ if not tcm_file_new: ++ sys.exit(1) ++ ++ print "Generated Target_Core_Mod config: " + tcm_file_new ++ ++ if overwrite != "1": ++ print "Not updating default config" ++ return ++ ++ if lio_active: ++ ret = shutil.copyfile(lio_file_new, lio_file) ++ if ret: ++ print "Unable to copy " + lio_file_new ++ sys.exit(1) ++ print "Successfully updated default config " + lio_file ++ ++ ret = shutil.copyfile(tcm_file_new, tcm_file) ++ if ret: ++ print "Unable to copy " + tcm_file_new ++ sys.exit(1) ++ ++ print "Successfully updated default config " + tcm_file ++ ++def tcm_overwrite_default(option, opt_str, value, parser): ++ ++ input = raw_input("Are you sure you want to overwrite the default configuration? Type 'yes': ") ++ if input != "yes": ++ sys.exit(0) ++ ++ val = "1" ++ tcm_full_backup(None, None, val, None) ++ ++def main(): ++ ++ parser = OptionParser() ++ parser.add_option("--b","--backup", action="callback", callback=tcm_full_backup, nargs=1, ++ type="string", dest="OVERWRITE", help="Do backup of TCM and storage fabric modules, and optionally overwrite default config data") ++ parser.add_option("--o","--overwrite", action="callback", callback=tcm_overwrite_default, nargs=0, ++ help="Overwrite default config data of TCM and storage fabric modules") ++ parser.add_option("--s","--stdout", action="callback", callback=tcm_dump_configfs, nargs=0, ++ help="Dump running Target_Core_Mod/ConfigFS syntax to STDOUT") ++ parser.add_option("--t", "--tofile", action="callback", callback=tcm_backup_to_file, nargs=1, ++ type="string", dest="DATE_TIME", help="Backup running Target_Core_Mod/ConfigFS syntax to /etc/target/backup/tcm_backup-.sh") ++ ++ (options, args) = parser.parse_args() ++ if len(sys.argv) == 1: ++ parser.print_help() ++ sys.exit(0) ++ elif not re.search('--', sys.argv[1]): ++ print "Unknown CLI option: " + sys.argv[1] ++ sys.exit(1) ++ ++if __name__ == "__main__": ++ main() +diff --git a/targetcli/tcm_fabric.py b/targetcli/tcm_fabric.py +new file mode 100644 +index 0000000..8a843d4 +--- /dev/null ++++ b/targetcli/tcm_fabric.py +@@ -0,0 +1,532 @@ ++import os, sys, shutil ++import subprocess as sub ++import string ++import re ++import datetime, time ++import optparse ++ ++target_root = "/sys/kernel/config/target/" ++spec_root = "/var/target/fabric/" ++ ++def fabric_configfs_dump(fabric_name, fabric_root, module_name): ++ ++ if not os.path.isdir(fabric_root): ++ print "Unable to access fabric_root: " + fabric_root ++ sys.exit(1) ++ ++ iqn_root = os.listdir(fabric_root) ++ ++ # This will load up the fabric module ++ print "modprobe " + module_name ++ print "mkdir " + fabric_root ++ ++# print "#### " + fabric_name + " Discovery authentication information" ++ auth_dir = fabric_root + "/discovery_auth" ++ if os.path.isdir(auth_dir) == True: ++ for auth in os.listdir(auth_dir): ++ if auth == "authenticate_target": ++ continue ++ ++ auth_file = auth_dir + "/" + auth ++ p = os.open(auth_file, 0) ++ value = os.read(p, 256) ++ ret = value.isspace() ++ if ret: ++ os.close(p) ++ continue ++ print "echo -n " + value.rstrip() + " > " + auth_file ++ os.close(p) ++ ++ iqn_root = os.listdir(fabric_root) ++ ++ # Loop through LIO-Target IQN list ++ for iqn in iqn_root: ++ if not os.path.isdir(fabric_root + "/" + iqn): ++ continue ++ if iqn == "lio_version": ++ continue ++ if iqn == "discovery_auth": ++ continue ++ ++ # Loop through LIO-Target IQN+TPGT list ++ tpg_root = os.listdir(fabric_root + "/" + iqn); ++ for tpgt_tmp in tpg_root: ++ if tpgt_tmp == "fabric_statistics": ++ continue ++ ++ tpgt_tmp2 = tpgt_tmp.split('_') ++ tpgt = tpgt_tmp2[1] ++ ++# print "#### Network portals for iSCSI Target Portal Group" ++# np_root = os.listdir(fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/np") ++# for np in np_root: ++# print "mkdir -p " + fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/np/" + np ++ ++ ++ # Dump Nexus attribute (when available) ++ nexus_file = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/nexus" ++ if os.path.isfile(nexus_file): ++ print "mkdir -p " + fabric_root + "/" + iqn + "/tpgt_" + tpgt ++ p = os.open(nexus_file, 0) ++ value = os.read(p, 256) ++ print "echo " + value.rstrip() + " > " + nexus_file ++ os.close(p) ++ ++ print "#### " + fabric_name + " Target Ports" ++ lun_root = os.listdir(fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/lun") ++ for lun_tmp in lun_root: ++ lun_tmp2 = lun_tmp.split('_') ++ lun = lun_tmp2[1] ++ ++ lun_dir = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun ++ print "mkdir -p " + lun_dir ++ ++ port_root = os.listdir(lun_dir) ++ for port in port_root: ++ if port == "alua_tg_pt_gp": ++ continue ++ if port == "alua_tg_pt_offline": ++ continue ++ if port == "alua_tg_pt_status": ++ continue ++ if port == "alua_tg_pt_write_md": ++ continue ++ ++ if not os.path.islink(lun_dir + "/" + port): ++ continue ++ ++ port_link = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/lun/lun_" + lun + "/" + port ++ sourcelink = os.readlink(port_link) ++ sourcelink2 = os.path.join(os.path.dirname(port_link), sourcelink) ++ print "ln -s " + sourcelink2 + " " + port_link ++ ++ # Dump ALUA Target Port Group ++ tg_pt_gp_file = lun_dir + "/alua_tg_pt_gp" ++ p = os.open(tg_pt_gp_file, 0) ++ try: ++ value = os.read(p, 512) ++ except: ++ os.close(p) ++ continue ++ os.close(p) ++ if value: ++ tg_pt_gp_tmp = value.split('\n') ++ tg_pt_gp_out = tg_pt_gp_tmp[0] ++ off = tg_pt_gp_out.index('Alias: ') ++ off += 7 # Skip over "Alias: " ++ tg_pt_gp_name = tg_pt_gp_out[off:] ++ # Only need to dump if LIO-Target Port is NOT partof ++ # the 'default_tg_pt_gp' ++ if not re.search(tg_pt_gp_name, 'default_tg_pt_gp'): ++ print "#### ALUA Target Port Group" ++ print "echo " + tg_pt_gp_name + " > " + tg_pt_gp_file ++ ++#FIXME: --aluasecmd support ++# print "lio_node --aluasecmd " + iqn + " " + tpgt + " " + lun ++ ++ # Dump values of iscsi/iqn/tpgt/attrib/ ++ print "#### Attributes for " + fabric_name + " Target Portal Group" ++ attrib_dir = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/attrib/" ++ attrib_root = os.listdir(attrib_dir) ++ for attrib in attrib_root: ++ attrib_file = attrib_dir + attrib ++ p = os.open(attrib_file, 0) ++ value = os.read(p, 16) ++ print "echo " + value.rstrip() + " > " + attrib_file ++ os.close(p) ++ ++ # Dump values for iscsi/iqn/tpgt/param ++ print "#### Parameters for " + fabric_name + " Target Portal Group" ++ param_dir = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/param/" ++ param_root = os.listdir(param_dir) ++ for param in param_root: ++ param_file = param_dir + param ++ p = os.open(param_file, 0) ++ value = os.read(p, 256) ++ print "echo \"" + value.rstrip() + "\" > " + param_file ++ os.close(p) ++ ++ if os.path.isfile(nexus_file): ++ continue ++ ++ # Dump fabric Initiator Node ACLs from fabric_root/$WWN/tpgt_$TPGT/acls/ ++ print "#### " + fabric_name + " Initiator ACLs for " + fabric_name + " Target Portal Group" ++ nacl_dir = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/acls/" ++ nacl_root = os.listdir(nacl_dir) ++ for nacl in nacl_root: ++ print "mkdir -p " + nacl_dir + nacl ++ ++ # Dump fabric Initiator ACL authentication info from fabric_root/$WWN/tpgt_$TPGT/acls//$INITIATOR/auth ++ print "#### " + fabric_name + " Initiator ACL authentication information" ++ auth_dir = nacl_dir + nacl + "/auth" ++ for auth in os.listdir(auth_dir): ++ if auth == "authenticate_target": ++ continue ++ auth_file = auth_dir + "/" + auth ++ p = os.open(auth_file, 0) ++ value = os.read(p, 256) ++ ret = value.isspace() ++ if ret: ++ os.close(p) ++ continue ++ print "echo -n " + value.rstrip() + " > " + auth_file ++ os.close(p) ++ ++ # Dump fabric Initiator ACL TPG attributes from fabric_root/$WWN/tpgt_$TPGT/acls/$INITIATOR/attrib ++ print "#### " + fabric_name + " Initiator ACL TPG attributes" ++ nacl_attrib_dir = nacl_dir + nacl + "/attrib" ++ for nacl_attrib in os.listdir(nacl_attrib_dir): ++ nacl_attrib_file = nacl_attrib_dir + "/" + nacl_attrib ++ p = os.open(nacl_attrib_file, 0) ++ value = os.read(p, 8) ++ print "echo " + value.rstrip() + " > " + nacl_attrib_file ++ os.close(p) ++ ++ # Dump fabric Initiator LUN ACLs from fabric_root/$WWN/tpgt_$TPGT//acls/$INITIATOR/lun ++ print "#### " + fabric_name + " Initiator LUN ACLs for iSCSI Target Portal Group" ++ lun_acl_dir = nacl_dir + nacl ++ for lun_acl in os.listdir(lun_acl_dir): ++ ret = re.search('lun_', lun_acl) ++ if not ret: ++ continue ++ lun_link_dir = nacl_dir + nacl + "/" + lun_acl ++ print "mkdir -p " + lun_link_dir ++ ++ for lun_acl_link in os.listdir(lun_link_dir): ++ if lun_acl_link == "write_protect": ++ p = os.open(lun_link_dir + "/write_protect", 0) ++ value = os.read(p, 4) ++ print "echo " + value.rstrip() + " > " + lun_link_dir + "/write_protect" ++ os.close(p) ++ continue ++ ++ if not os.path.islink(lun_link_dir + "/" + lun_acl_link): ++ continue ++ ++ sourcelink = os.readlink(lun_link_dir + "/" + lun_acl_link) ++ sourcelink2 = os.path.join(os.path.dirname(lun_link_dir + "/" + lun_acl_link), sourcelink) ++ print "ln -s " + sourcelink2 + " " + lun_link_dir + "/" + lun_acl_link ++ ++ # Dump value of fabric_root/$WWN/tpgt_$TPGT//enable ++ print "#### Trigger to enable " + fabric_name + " Target Portal Group" ++ enable_file = fabric_root + "/" + iqn + "/tpgt_" + tpgt + "/enable" ++ if os.path.isfile(enable_file): ++ p = os.open(enable_file, 0) ++ value = os.read(p, 1) ++ print "echo " + value.rstrip() + " > " + enable_file ++ os.close(p) ++ ++ ++ return ++ ++def fabric_configfs_dump_all(): ++ ++ for fabric_name in os.listdir(target_root): ++ if fabric_name == "version": ++ continue ++ if fabric_name == "core": ++ continue ++ # FIXME: currently using lio_dump --stdout ++ if fabric_name == "iscsi": ++ continue ++ ++ fabric_root = target_root + fabric_name ++# print "Using fabric_configfs_dump_all: " + fabric_name + ", " + fabric_root ++ module_name = fabric_get_module_name(fabric_name) ++# print "module_name: "+ module_name ++ ++ fabric_configfs_dump(fabric_name, fabric_root, module_name); ++ ++ return ++ ++def fabric_backup_to_file(date_time, fabric_name, fabric_root, module_name): ++ now = date_time ++ ++ if not os.path.isdir(fabric_root): ++ print "Unable to access fabric_root: " + fabric_root ++ sys.exit(1) ++ ++ current_dir = "/etc/target" ++ backup_dir = "/etc/target/backup" ++ if not os.path.isdir(backup_dir): ++ op = "mkdir " + backup_dir ++ ret = os.system(op) ++ if ret: ++ print "Unable to open backup_dir" ++ sys.exit(1) ++ ++ op = "tcm_fabric --stdout --fabric-name=" + fabric_name + " --fabric-root=" + fabric_root + " --module-name=" + module_name ++# print "Using op: " + op ++ p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout ++ if not p: ++ print "Unable to dump " + fabric_name + "/ConfigFS running state" ++ sys.exit(1) ++ ++ orig_file = current_dir + "/" + fabric_name + "_start.sh" ++ ++ print "Making backup of " + fabric_name + "/ConfigFS with timestamp: " + now ++ backup_file = backup_dir + "/" + fabric_name + "_backup-" + now + ".sh" ++ if os.path.isfile(backup_file): ++ print "" + fabric_name + " backup_file: " + backup_file + "already exists, exiting" ++ p.close() ++ sys.exit(1) ++ ++ back = open(backup_file, 'w') ++ ++ line = p.readline() ++ while line: ++ print >>back, line.rstrip() ++ line = p.readline() ++ ++ p.close() ++ back.close() ++ ++ ret = shutil.copyfile(backup_file, orig_file) ++ if ret: ++ print "Unable to copy " + back_file ++ sys.exit(1) ++ ++ print "Successfully updated default config " + orig_file ++ ++ return backup_file ++ ++def fabric_backup_to_file_all(date_time): ++ ++ if not os.path.isdir(target_root): ++ print "Unable to open target_root: " + target_root ++ sys.exit(1) ++ ++ for fabric_name in os.listdir(target_root): ++ if fabric_name == "version": ++ continue ++ if fabric_name == "core": ++ continue ++ # FIXME: currently using lio_dump ++ if fabric_name == "iscsi": ++ continue ++ ++ fabric_root = target_root + fabric_name ++# print "Using fabric_backup_to_file: " + date_time + ", " + fabric_name + ", " + fabric_root ++ module_name = fabric_get_module_name(fabric_name) ++# print "Using module_name: "+ module_name ++ ++ fabric_backup_to_file(date_time, fabric_name, fabric_root, module_name) ++ ++ ++ return ++ ++def fabric_unload(fabric_name, fabric_root, module_name): ++ ++ if not os.path.isdir(fabric_root): ++ print "Unable to access fabric_root: " + fabric_root ++ sys.exit(1) ++ ++ wwn_root = os.listdir(fabric_root) ++ for wwn in wwn_root: ++ if not os.path.isdir(fabric_root + "/" + wwn): ++ continue ++ if wwn == "discovery_auth": ++ continue ++ ++ tpg_root = fabric_root + "/" + wwn ++ for tpgt_tmp in os.listdir(tpg_root): ++ if tpgt_tmp == "fabric_statistics": ++ continue ++ ++ tpgt_tmp2 = tpgt_tmp.split('_') ++ tpgt = tpgt_tmp2[1] ++ ++ if os.path.isfile(fabric_root + "/" + wwn + "/tpgt_" + tpgt + "/enable"): ++ disable_op = "echo 0 > " + fabric_root + "/" + wwn + "/tpgt_" + tpgt + "/enable" ++ ret = os.system(disable_op) ++ if ret: ++ print "Unable to disable TPG: " + wwn + " TPGT: " + tpgt ++ ++ nacl_root = fabric_root + "/" + wwn + "/tpgt_" + tpgt + "/acls" ++ for nacl in os.listdir(nacl_root): ++ lun_acl_root = nacl_root + "/" + nacl + "/" ++ for lun_acl in os.listdir(lun_acl_root): ++ ret = re.search('lun_', lun_acl) ++ if not ret: ++ continue ++ mapped_lun = lun_acl[4:] ++ ++ lun_link_dir = lun_acl_root + "/" + lun_acl + "/" ++ for lun_acl_link in os.listdir(lun_link_dir): ++ if lun_acl_link == "write_protect": ++ continue ++ ++ if os.path.islink(lun_link_dir + "/" + lun_acl_link): ++ unlink_op = "unlink " + lun_link_dir + "/" + lun_acl_link ++ ret = os.system(unlink_op) ++ if ret: ++ print "Unable to unlink MappedLUN: " + lun_link_dir + "/" + lun_acl_link ++ ++ dellunacl_op = "rmdir " + lun_link_dir ++ ret = os.system(dellunacl_op) ++ if ret: ++ print "Unable to rmdir fabric mapped_lun" ++ ++ delnodeacl_op = "rmdir " + nacl_root + "/" + nacl + "/" ++ ret = os.system(delnodeacl_op) ++ if ret: ++ print "Unable to remove NodeACL: " + nacl_root + "/" + nacl + "/" ++ ++ lun_root = fabric_root + "/" + wwn + "/tpgt_" + tpgt + "/lun" ++ for lun_tmp in os.listdir(lun_root): ++ lun_tmp2 = lun_tmp.split('_') ++ lun = lun_tmp2[1] ++ ++ lun_dir = lun_root + "/lun_" + lun ++ for port in os.listdir(lun_dir): ++ if not os.path.islink(lun_dir + "/" + port): ++ continue ++ ++ unlink_op = "unlink " + lun_dir + "/" + port ++ ret = os.system(unlink_op) ++ if ret: ++ print "Unable to unlink fabric port/lun" ++ ++ rmdir_op= "rmdir " + lun_dir ++ ret = os.system(rmdir_op); ++ if ret: ++ print "Unable to rmdir fabric port/lun: " + lun_dir ++ ++ ++ rmdir_op = "rmdir " + fabric_root + "/" + wwn + "/tpgt_" + tpgt + "/" ++ ret = os.system(rmdir_op) ++ if ret: ++ print "Unable to rmdir fabric tpg: " + fabric_root + "/" + wwn + "/tpgt_" + tpgt + "/" ++ ++ rmdir_op = "rmdir " + fabric_root + "/" + wwn + "/" ++ ret = os.system(rmdir_op) ++ if ret: ++ print "Unable to rmdir fabric wwn: " + fabric_root + "/" + wwn + "/" ++ ++ ++ ++ rmdir_op = "rmdir " + fabric_root ++ ret = os.system(rmdir_op) ++ if ret: ++ print "Unable to release fabric_root: " + fabric_root ++ ++ rmmod_op = "rmmod " + module_name ++ ret = os.system(rmmod_op) ++ if ret: ++ print "Unable to unload " + module_name ++ ++ print "Successfully released fabric: " + fabric_root ++ return ++ ++def fabric_get_module_name(fabric_name): ++ kernel_module = "" ++ ++ for specs in os.listdir(spec_root): ++ if specs == "README": ++ continue ++# print "specs: " + specs + ", fabric_name: " + fabric_name ++ ++ if not re.search(fabric_name + ".spec", specs) and not re.search("tcm_" + fabric_name + ".spec", specs) and not re.search(fabric_name, specs): ++ continue ++ ++ op = "cat " + spec_root + specs ++ p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout ++ if not p: ++ print "Unable to dump " + fabric_name + "/ConfigFS running state" ++ sys.exit(1) ++ ++ line = p.readline() ++ while line: ++ tmp = line.rstrip() ++ # Check for 'kernel_module' line in $FABRIC.spec ++ if re.search('kernel_module', tmp): ++ tmp_list = tmp.split('= ') ++ p.close() ++ return tmp_list[1] ++ ++ line = p.readline() ++ ++ p.close() ++ ++ return kernel_module ++ ++def fabric_unloadall(): ++ ++ module_name = "" ++ ++ for fabric_name in os.listdir(target_root): ++ if fabric_name == "version": ++ continue ++ if fabric_name == "core": ++ continue ++ # FIXME: currently using lio_node --unload ++ if fabric_name == "iscsi": ++ continue ++ ++ fabric_root = target_root + fabric_name ++ module_name = fabric_get_module_name(fabric_name) ++# print "fabric_get_module_name() using: " + module_name ++ ++ if module_name == "": ++ continue ++ ++ fabric_unload(fabric_name, fabric_root, module_name) ++ ++ ++def do_work(stdout_enable, stdout_enable_all, date_time, unload, unloadall, fabric_name, fabric_root, module_name): ++ ++ if not stdout_enable == "None": ++ fabric_configfs_dump(fabric_name, fabric_root, module_name) ++ elif not stdout_enable_all == "None": ++ fabric_configfs_dump_all() ++ elif not date_time == "None": ++ fabric_backup_to_file(date_time, fabric_name, fabric_root, module_name) ++ elif not unload == "None": ++ fabric_unload(fabric_name, fabric_root, module_name) ++ elif not unloadall == "None": ++ fabric_unloadall() ++ ++ return 0 ++ ++def main(): ++ ++ parser_fabric = optparse.OptionParser() ++ parser_fabric.add_option("--s","--stdout", dest='stdout_enable', action='store', nargs=0, ++ help="Dump running Fabric/ConfigFS syntax to STDOUT", type='string') ++ parser_fabric.add_option("--z","--stdoutall", dest='stdout_enable_all', action='store', nargs=0, ++ help="Dump all running Fabric/ConfigFS syntax to STDOUT", type='string') ++ parser_fabric.add_option("--t", "--tofile", dest="date_time", action='store', nargs=1, ++ help="Backup running Fabric/ConfigFS syntax to /etc/target/backup/fabricname_backup-.sh", ++ type='string') ++ parser_fabric.add_option("--u", "--unload", dest="unload", action='store', nargs=0, ++ help="Unload running Fabric/ConfigFS", type='string') ++ parser_fabric.add_option("--a", "--unloadall", dest="unloadall", action='store', nargs=0, ++ help="Unload all running Fabric/ConfigFS", type='string') ++ parser_fabric.add_option("--f", "--fabric-name", dest='fabric_name', action='store', nargs=1, ++ help="Target fabric name", type='string') ++ parser_fabric.add_option("--r", "--fabric-root", dest='fabric_root', action='store', nargs=1, ++ help="Target fabric configfs root", type='string') ++ parser_fabric.add_option("--m", "--module-name", dest='module_name', action='store', nargs=1, ++ help="Target fabric module name ", type='string') ++ ++ (opts_fabric, args_fabric) = parser_fabric.parse_args() ++ ++ mandatories = ['fabric_name', 'fabric_root', 'module_name'] ++ for m in mandatories: ++ if not opts_fabric.__dict__[m]: ++ unloadall = str(opts_fabric.__dict__['unloadall']) ++ stdout_enable = str(opts_fabric.__dict__['stdout_enable']) ++ stdout_enable_all = str(opts_fabric.__dict__['stdout_enable_all']) ++ date_time = str(opts_fabric.__dict__['date_time']) ++ if unloadall == "None" and stdout_enable == "None" and stdout_enable_all == "None" and date_time == "None": ++ print "mandatory option is missing\n" ++ parser_fabric.print_help() ++ exit(-1) ++ ++ do_work(str(opts_fabric.stdout_enable), str(opts_fabric.stdout_enable_all), ++ str(opts_fabric.date_time), str(opts_fabric.unload), str(opts_fabric.unloadall), ++ str(opts_fabric.fabric_name), str(opts_fabric.fabric_root), ++ str(opts_fabric.module_name)) ++ ++if __name__ == "__main__": ++ main() +diff --git a/targetcli/tcm_fileio.py b/targetcli/tcm_fileio.py +new file mode 100644 +index 0000000..839d91a +--- /dev/null ++++ b/targetcli/tcm_fileio.py +@@ -0,0 +1,83 @@ ++import os ++import subprocess as sub ++import string, re ++from optparse import OptionParser ++ ++tcm_root = "/sys/kernel/config/target/core" ++ ++def createvirtdev(path, params): ++ ++# print "Calling fileio createvirtdev: path " + path ++ cfs_path = tcm_root + "/" + path + "/" ++# print "Calling fileio createvirtdev: params " + str(params) ++ fd_params = str(params) ++ ++ # Extract the udev_dev path from fd_dev_name= ++ try: ++ off = fd_params.index('fd_dev_name=') ++ off += 12 ++ file_tmp = fd_params[off:] ++ file = file_tmp.split(',') ++ except IOError, msg: ++ print "Unable to locate fd_dev_name= parameter key" ++ return -1 ++ ++ # Set UDEV path if struct file is pointing to an underlying struct block_device ++ if re.search('/dev/', file[0]): ++ udev_path = file[0] ++ set_udev_path_op = "echo -n " + udev_path + " > " + cfs_path + "udev_path" ++ ret = os.system(set_udev_path_op) ++ if ret: ++ print "pSCSI: Unable to set udev_path in " + cfs_path + " for: " + udev_path ++ return -1 ++ ++ control_opt = "echo -n " + params[0] + " > " + cfs_path + "control" ++# print "control_opt: " + control_opt ++ ret = os.system(control_opt) ++ if ret: ++ print "FILEIO: createvirtdev failed for control_opt with " + params[0] ++ return -1 ++ ++ enable_opt = "echo 1 > " + cfs_path + "enable" ++# print "Calling enable_opt " + enable_opt ++ ret = os.system(enable_opt) ++ if ret: ++ print "FILEIO: createvirtdev failed for enable_opt with " + params[0] ++ return -1 ++ ++def fd_freevirtdev(): ++ pass ++ ++def fd_get_params(path): ++ # Reference by udev_path if available ++ udev_path_file = path + "/udev_path" ++ p = os.open(udev_path_file, 0) ++ value = os.read(p, 1024) ++ if re.search('/dev/', value): ++ os.close(p) ++ # Append a FILEIO size of ' 0', as struct block_device sector count is autodetected by TCM ++ return "fd_dev_name=" + value.rstrip() + ",fd_dev_size=0" ++ ++ os.close(p) ++ ++ info_file = path + "/info" ++ p = open(info_file, 'rU') ++ try: ++ value = p.read(1024) ++ except IOError, msg: ++ p.close() ++ return ++ p.close() ++ ++ off = value.index('File: ') ++ off += 6 ++ fd_dev_name_tmp = value[off:] ++ fd_dev_name = fd_dev_name_tmp.split(' ') ++ off = value.index(' Size: ') ++ off += 7 ++ fd_dev_size_tmp = value[off:] ++ fd_dev_size = fd_dev_size_tmp.split(' ') ++ params = "fd_dev_name=" + fd_dev_name[0] + ",fd_dev_size=" + fd_dev_size[0] ++ ++ # fd_dev_name= and fd_dev_size= parameters for tcm_node --createdev ++ return params +diff --git a/targetcli/tcm_iblock.py b/targetcli/tcm_iblock.py +new file mode 100644 +index 0000000..f522dd2 +--- /dev/null ++++ b/targetcli/tcm_iblock.py +@@ -0,0 +1,86 @@ ++import os, tempfile ++import subprocess as sub ++import string, re ++from optparse import OptionParser ++ ++tcm_root = "/sys/kernel/config/target/core" ++ ++def createvirtdev(path, params): ++# print "Calling iblock createvirtdev: path " + path ++ cfs_path = tcm_root + "/" + path + "/" ++# print "Calling iblock createvirtdev: params " + str(params) ++ path = params[0] ++ if not re.search('/dev/', path): ++ print "IBLOCK: Please reference a valid /dev/ block_device" ++ return -1 ++ ++ udev_path = path.rstrip() ++ # Resolve symbolic links to get major/minor ++ udev_op = "/bin/ls -laL " + udev_path ++ p = sub.Popen(udev_op, shell=True, stdout=sub.PIPE).stdout ++ line = p.readline() ++ out = line.split(' '); ++ major = out[4] ++ minor = out[5] ++ p.close() ++ ++ if major == "11,": ++ print "Unable to export Linux/SCSI TYPE_CDROM from IBLOCK, please use pSCSI export" ++ return -1 ++ if major == "22,": ++ print "Unable to export IDE CDROM from IBLOCK" ++ return -1 ++ ++ set_udev_path_op = "echo -n " + udev_path + " > " + cfs_path + "udev_path" ++ ret = os.system(set_udev_path_op) ++ if ret: ++ print "IBLOCK: Unable to set udev_path in " + cfs_path + " for: " + udev_path ++ return -1 ++ ++ control_opt = "echo -n udev_path=" + udev_path + " > " + cfs_path + "control" ++ ret = os.system(control_opt) ++ if ret: ++ print "IBLOCK: createvirtdev failed for control_opt with " + control_opt ++ return -1 ++ ++ enable_opt = "echo 1 > " + cfs_path + "enable" ++ ret = os.system(enable_opt) ++ if ret: ++ print "IBLOCK: createvirtdev failed for enable_opt with " + enable_opt ++ return -1 ++ ++def iblock_freevirtdev(): ++ pass ++ ++def iblock_get_params(path): ++ # Reference by udev_path if available ++ udev_path_file = path + "/udev_path" ++ p = os.open(udev_path_file, 0) ++ value = os.read(p, 1024) ++ if re.search('/dev/', value): ++ os.close(p) ++ return value.rstrip() ++ ++ os.close(p) ++ ++ info_file = path + "/info" ++ p = open(info_file, 'rU') ++ try: ++ value = p.read(1024) ++ except IOError, msg: ++ p.close() ++ return ++ p.close() ++ ++ of = value.index('Major: ') ++ off += 7 ++ major_tmp = value[off:] ++ major = major_tmp.split(' ') ++ off = value.index('Minor: ') ++ off += 7 ++ minor_tmp = value[off:] ++ minor = minor_tmp.split(' ') ++ params = "major=" + major[0] + ",minor=" + minor[0] ++ os.close(p) ++ ++ return params +diff --git a/targetcli/tcm_loop.py b/targetcli/tcm_loop.py +new file mode 100644 +index 0000000..aeb211a +--- /dev/null ++++ b/targetcli/tcm_loop.py +@@ -0,0 +1,242 @@ ++import os, sys ++import subprocess as sub ++import string ++import re ++from optparse import OptionParser ++ ++tcm_loop_root = "/sys/kernel/config/target/loopback/" ++tcm_root = "/sys/kernel/config/target/core" ++ ++def tcm_generate_naa_sas_address(): ++ # Use NAA IEEE Registered Designator prefix, and append WWN UUID below ++ sas_address = "naa.6001405" ++ ++ uuidgen_op = 'uuidgen' ++ p = sub.Popen(uuidgen_op, shell=True, stdout=sub.PIPE).stdout ++ uuid = p.readline() ++ p.close() ++ ++ if not uuid: ++ print "Unable to generate UUID using uuidgen, continuing anyway" ++ sys.exit(1) ++ ++ val = uuid.rstrip(); ++ sas_address += val[:10] ++ sas_address = sas_address.replace('-','') ++ ++ return sas_address ++ ++def tcm_loop_add_target_www(option, opt_str, value, parser): ++ sas_target_address = tcm_generate_naa_sas_address(); ++ sas_target_tpgt = str(value) ++ ++def tcm_loop_del_target_wwn(option, opt_str, value, parser): ++ sas_target_address = str(value[0]) ++ sas_target_tpgt = str(value[1]) ++ ++ tpgt_dir = tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt ++ delete_op = "rmdir " + tpgt_dir ++ ret = os.system(delete_op) ++ if ret: ++ print "Unable to remove configfs group: " + tpgt_dir ++ ++ naa_dir = tcm_loop_root + sas_target_address ++ delete_op = "rmdir " + naa_dir ++ ret = os.system(delete_op) ++ if ret: ++ print "Unable to remove configfs group: " + naa_dir ++ else: ++ print "Successfully removed NAA based SAS Target Address: " + naa_dir + "/" + tpgt_dir ++ ++def tcm_loop_create_nexus(option, opt_str, value, parser): ++ sas_target_address = tcm_generate_naa_sas_address(); ++ sas_target_tpgt = str(value) ++ sas_initiator_address = tcm_generate_naa_sas_address(); ++ ++ tpgt_dir = tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt ++ create_op = "mkdir -p " + tpgt_dir ++ ret = os.system(create_op) ++ if ret: ++ print "Unable to create virtual Target Port: " + create_op ++ sys.exit(1) ++ ++ # In TCM_Loop 4.x code, there is an nexus configfs attribute instead of ++ # nexus configfs group ++ nexus_dir = tpgt_dir + "/nexus" ++ if os.path.isfile(nexus_dir): ++ create_op = "echo " + sas_initiator_address + " > " + nexus_dir ++ else: ++ create_op = "mkdir -p " + nexus_dir + "/" + sas_initiator_address ++ ++ ret = os.system(create_op) ++ if ret: ++ print "Unable to create virtual SAS I_T Nexus: " + create_op ++ sys.exit(1) ++ else: ++ print "Successfully created virtual SCSI I_T Nexus between TCM and Linux/SCSI HBA" ++ print " SAS Target Address: " + sas_target_address ++ print " SAS Initiator Address " + sas_initiator_address ++ ++def tcm_loop_delete_nexus(option, opt_str, value, parser): ++ sas_target_address = str(value[0]) ++ sas_target_tpgt = str(value[1]) ++ sas_initiator_address = ""; ++ ++ nexus_dir = tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt + "/nexus" ++ ++ if os.path.isfile(nexus_dir): ++ delete_op = "echo NULL > " + nexus_dir ++ ++ ret = os.system(delete_op) ++ if ret: ++ print "Unable to delete virtual SCSI I_T Nexus between TCM and Linux/SCSI HBA" ++ sys.exit(1) ++ ++ print "Successfully deleted virtual SCSI I_T Nexus between TCM and Linux/SCSI HBA" ++ return ++ ++ for nexus in os.listdir(nexus_dir): ++ delete_op = "rmdir " + nexus_dir + "/" + nexus ++ ++ ret = os.system(delete_op) ++ if ret: ++ print "Unable to delete virtual SCSI I_T Nexus between TCM and Linux/SCSI HBA" ++ sys.exit(1) ++ ++ print "Successfully deleted virtual SCSI I_T Nexus between TCM and Linux/SCSI HBA" ++ return ++ ++def tcm_loop_addlun(option, opt_str, value, parser): ++ sas_target_address = str(value[0]) ++ sas_target_tpgt = str(value[1]) ++ sas_target_lun = str(value[2]) ++ ++ mkdir_op = "mkdir -p " + tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt + "/lun/lun_" + sas_target_lun ++ ret = os.system(mkdir_op) ++ if ret: ++ print "Unable to create SAS Target Port LUN configfs group: " + mkdir_op ++ sys.exit(1) ++ ++ tcm_obj = str(value[3]); ++ port_src = tcm_root + "/" + tcm_obj ++ port_dst = tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt + "/lun/lun_" + sas_target_lun + "/virtual_scsi_port" ++ ++ link_op = "ln -s " + port_src + " " + port_dst ++ ret = os.system(link_op) ++ if not ret: ++ print "Successfully created SAS Target Port to local virtual SCSI Logical Unit" ++ # FIXME Add tcm_loop_alua_check_secondary_md() ++ # FIXME Add tcm_loop_alua_set_secondary_write_md() ++ else: ++ print "Unable to create SAS Target Port to local virtual SCSI Logical Unit" ++ sys.exit(1) ++ ++def tcm_loop_dellun(option, opt_str, value, parser): ++ sas_target_address = str(value[0]) ++ sas_target_tpgt = str(value[1]) ++ sas_target_lun = str(value[2]) ++ ++ port_link = "" ++ ++ lun_dir = tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt + "/lun/lun_" + sas_target_lun ++ if not os.path.isdir(lun_dir): ++ print "TCM_Loop lun_dir: " + lun_dir + " does not exist" ++ sys.exit(1) ++ ++ # Locate the port symlink, skipping over the per TCM port alua_* attributes ++ for port in os.listdir(lun_dir): ++ port_link_tmp = lun_dir + "/" + port ++ if not os.path.islink(port_link_tmp): ++ continue ++ ++ port_link = port_link_tmp ++ break ++ ++ if port_link == "": ++ print "Active TCM_Loop port link does not exist!" ++ sys.exit(1) ++ ++ unlink_op = "unlink " + port_link ++ ret = os.system(unlink_op) ++ if ret: ++ print "Unable to unlink port for virtual SCSI Logical Unit: " + port ++ sys.exit(1) ++ ++ rmdir_op = "rmdir " + tcm_loop_root + sas_target_address + "/tpgt_" + sas_target_tpgt + "/lun/lun_" + sas_target_lun ++ ret = os.system(rmdir_op) ++ if ret: ++ print "Unable to rmdir configfs group for virtual SCSI Logical Unit: " + port ++ sys.exit(1) ++ else: ++ print "Succesfully deleted local virtual SCSI Logical Unit from SAS Target Port" ++ ++def tcm_loop_unload(option, opt_str, value, parser): ++ ++ for sas_target_naa in os.listdir(tcm_loop_root): ++ print "sas_target_naa: " + sas_target_naa ++ ++ if os.path.isfile(tcm_loop_root + sas_target_naa) == True: ++ continue ++ ++ tpgt_dir = tcm_loop_root + sas_target_naa + "/" ++ for sas_target_tpgt in os.listdir(tpgt_dir): ++ if sas_target_tpgt == "fabric_statistics": ++ continue ++ ++ print "sas_target_tpgt: " + sas_target_tpgt ++ ++ lun_dir = tpgt_dir + "/" + sas_target_tpgt + "/lun/" ++ for sas_target_lun in os.listdir(lun_dir): ++ ++ print "sas_target_lun: " + sas_target_lun ++ tpgt = sas_target_tpgt[5:] ++ lun = sas_target_lun[4:] ++ vals = [sas_target_naa, tpgt, lun] ++ tcm_loop_dellun(None, None, vals, None) ++ ++ tpgt = sas_target_tpgt[5:] ++ vals = [sas_target_naa, tpgt] ++ ++ tcm_loop_delete_nexus(None, None, vals, None) ++ ++ tcm_loop_del_target_wwn(None, None, vals, None) ++ ++ rmdir_op = "rmdir " + tcm_loop_root ++ ret = os.system(rmdir_op) ++ if ret: ++ print "Unable to remove tcm_loop_root configfs group: " + tcm_loop_root ++ sys.exit(1) ++ ++ rmmod_op = "rmmod tcm_loop" ++ ret = os.system(rmmod_op) ++ if ret: ++ print "Unable to remove tcm_loop kernel module" ++ sys.exit(1) ++ ++ print "Successfully removed tcm_loop kernel module" ++ ++def main(): ++ ++ parser = OptionParser() ++ parser.add_option("--delwwn", action="callback", callback=tcm_loop_del_target_wwn, nargs=2, ++ type="string", dest="NAA_TARGET_WWN TPGT", help="Delete a SAS Virtual HBA by WWN+TPGT") ++ parser.add_option("--createnexus", action="callback", callback=tcm_loop_create_nexus, nargs=1, ++ type="string", dest="TPGT", help="Create a virtual SAS I_T Nexus using generated NAA WWN for SAS Address. This will create a new Linux/SCSI Host Bus Adapter for the I_T Nexus"); ++ parser.add_option("--delnexus", action="callback", callback=tcm_loop_delete_nexus, nargs=2, ++ type="string", dest="NAA_TARGET_WWN TPGT", help="Delete a virtual SAS I_T Nexus"); ++ parser.add_option("--addlun", action="callback", callback=tcm_loop_addlun, nargs=4, ++ type="string", dest="NAA_TARGET_WWN TPGT LUN HBA/DEV", help="Add virtual SCSI Linux to NAA Target/Initiator Sas Addresses") ++ parser.add_option("--dellun", action="callback", callback=tcm_loop_dellun, nargs=3, ++ type="string", dest="NAA_TARGET_WWN TPGT LUN", help="Delete Target SAS Port to virtual SCSI Logical unit mapping") ++ parser.add_option("--unload", action="callback", callback=tcm_loop_unload, nargs=0, ++ help="Shutdown all virtual SCSI LUNs and unload tcm_loop") ++ ++ (options, args) = parser.parse_args() ++ if len(sys.argv) == 1: ++ parser.print_help() ++ sys.exit(0) ++ elif not re.search('--', sys.argv[1]): ++ lio_err("Unknown CLI option: " + sys.argv[1]) ++ ++if __name__ == "__main__": ++ main() +diff --git a/targetcli/tcm_node.py b/targetcli/tcm_node.py +new file mode 100644 +index 0000000..9df6f40 +--- /dev/null ++++ b/targetcli/tcm_node.py +@@ -0,0 +1,737 @@ ++from __future__ import with_statement ++ ++import os, sys, signal ++import subprocess as sub ++import string ++import re ++import errno ++import uuid ++import shutil ++from optparse import OptionParser ++ ++import tcm_pscsi ++import tcm_iblock ++import tcm_ramdisk ++import tcm_fileio ++ ++tcm_root = "/sys/kernel/config/target/core" ++ ++def tcm_err(msg): ++ print >> sys.stderr, msg ++ sys.exit(1) ++ ++def tcm_read(filename): ++ with open(filename) as f: ++ return f.read() ++ ++def tcm_write(filename, value, newline=True): ++ with open(filename, "w") as f: ++ f.write(value) ++ if newline: ++ f.write("\n") ++ ++def tcm_full_path(arg): ++ return tcm_root + "/" + arg ++ ++def tcm_check_dev_exists(dev_path): ++ full_path = tcm_full_path(dev_path) ++ if not os.path.isdir(full_path): ++ tcm_err("TCM/ConfigFS storage object does not exist: " + full_path) ++ ++def tcm_add_alua_lugp(gp_name): ++ os.makedirs(tcm_root + "/alua/lu_gps/" + gp_name) ++ ++ try: ++ tcm_write(tcm_root + "/alua/lu_gps/%s/lu_gp_id" % lu_gp_name, lu_gp_name) ++ except: ++ os.rmdir(tcm_root + "/alua/lu_gps/" + lu_gp_name) ++ raise ++ ++def tcm_add_alua_tgptgp(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ alua_cfs_path = tcm_full_path(dev_path) + "alua/" + gp_name + "/" ++ ++ os.makedirs(alua_cfs_path) ++ ++ try: ++ tcm_write(alua_cfs_path + "tg_pt_gp_id", "0") ++ except: ++ os.rmdir(alua_cfs_path) ++ raise ++ ++def tcm_alua_check_metadata_dir(dev_path): ++ alua_path = "/var/target/alua/tpgs_" + tcm_get_unit_serial(dev_path) + "/" ++ if os.path.isdir(alua_path): ++ return ++ ++ # Create the ALUA metadata directory for the passed storage object ++ # if it does not already exist. ++ os.makedirs(alua_path) ++ ++def tcm_alua_delete_metadata_dir(unit_serial): ++ try: ++ os.rmdir("/var/target/alua/tpgs_" + unit_serial + "/") ++ except OSError: ++ pass ++ ++def tcm_alua_process_metadata(dev_path, gp_name, gp_id): ++ alua_gp_path = tcm_full_path(dev_path) + "/alua/" + gp_name ++ alua_md_path = "/var/target/alua/tpgs_" + tcm_get_unit_serial(dev_path) \ ++ + "/" + gp_name ++ ++ if not os.path.isfile(alua_md_path): ++ # If not pre-existing ALUA metadata exists, go ahead and ++ # allow new ALUA state changes to create and update the ++ # struct file metadata ++ tcm_write(alua_gp_path + "/alua_write_metadata", "1") ++ return ++ ++ with open(alua_md_path, 'rU') as p: ++ d = dict() ++ for line in p.readlines(): ++ name, value = line.split("=") ++ d[name.strip()] = value.strip() ++ ++ if "tg_pt_gp_id" in d and int(d["tg_pt_gp_id"]) != int(gp_id): ++ raise IOError("Passed tg_pt_gp_id: %s does not match extracted: %s" % \ ++ (gp_id, d["tg_pt_gp_id"])) ++ ++ if "alua_access_state" in d: ++ tcm_write(alua_gp_path + "/alua_access_state", d["alua_access_state"]) ++ ++ if "alua_access_status" in d: ++ tcm_write(alua_gp_path + "/alua_access_status", d["alua_access_status"]) ++ ++ # Now allow changes to ALUA target port group update the struct file metadata ++ # in /var/target/alua/tpgs_$T10_UNIT_SERIAL/$TG_PT_GP_NAME ++ tcm_write(alua_gp_path + "/alua_write_metadata", "1") ++ ++def tcm_add_alua_tgptgp_with_md(dev_path, gp_name, gp_id): ++ alua_gp_path = tcm_full_path(dev_path) + "/alua/" + gp_name ++ ++ tcm_check_dev_exists(dev_path) ++ ++ # If the default_tg_pt_gp is passed, we skip the creation (as it already exists) ++ # and just process ALUA metadata ++ if gp_name == 'default_tg_pt_gp' and gp_id == '0': ++ tcm_alua_process_metadata(dev_path, gp_name, gp_id) ++ return ++ ++ os.makedirs(alua_gp_path) ++ ++ try: ++ tcm_write(alua_gp_path + "/tg_pt_gp_id", gp_id) ++ except: ++ os.rmdir(alua_gp_path) ++ raise ++ ++ # Now process the ALUA metadata for this group ++ tcm_alua_process_metadata(dev_path, gp_name, gp_id) ++ ++def tcm_delhba(hba_name): ++ hba_path = tcm_full_path(hba_name) ++ ++ for g in os.listdir(hba_path): ++ if g == "hba_info" or g == "hba_mode": ++ continue ++ ++ __tcm_freevirtdev(hba_name + "/" + g) ++ ++ os.rmdir(hba_path) ++ ++def tcm_del_alua_lugp(lu_gp_name): ++ if not os.path.isdir(tcm_root + "/alua/lu_gps/" + lu_gp_name): ++ tcm_err("ALUA Logical Unit Group: " + lu_gp_name + " does not exist!") ++ ++ os.rmdir(tcm_root + "/alua/lu_gps/" + lu_gp_name) ++ ++def __tcm_del_alua_tgptgp(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ full_path = tcm_full_path(dev_path) ++ ++ if not os.path.isdir(full_path + "/alua/" + gp_name): ++ tcm_err("ALUA Target Port Group: " + gp_name + " does not exist!") ++ ++ os.rmdir(full_path + "/alua/" + gp_name) ++ ++# deletes configfs entry for alua *and* metadata dir. ++def tcm_del_alua_tgptgp(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ alua_md_path = "/var/target/alua/tpgs_" + tcm_get_unit_serial(dev_path) \ ++ + "/" + gp_name ++ ++ __tcm_del_alua_tgptgp(dev_path, gp_name) ++ ++ if not os.path.isfile(alua_md_path): ++ return ++ ++ shutil.rmtree(alua_md_path) ++ ++def tcm_generate_uuid_for_unit_serial(dev_path): ++ # Generate random uuid ++ tcm_set_wwn_unit_serial(dev_path, str(uuid.uuid4())) ++ ++tcm_types = ( \ ++ dict(name="pscsi", module=tcm_pscsi, gen_uuid=False), ++ dict(name="stgt", module=None, gen_uuid=True), ++ dict(name="iblock", module=tcm_iblock, gen_uuid=True), ++ dict(name="rd_dr", module=tcm_ramdisk, gen_uuid=True), ++ dict(name="rd_mcp", module=tcm_ramdisk, gen_uuid=True), ++ dict(name="fileio", module=tcm_fileio, gen_uuid=True), ++) ++ ++def tcm_createvirtdev(dev_path, plugin_params, establishdev=False): ++ hba_path = dev_path.split('/')[0] ++ ++ # create hba if it doesn't exist ++ hba_full_path = tcm_full_path(hba_path) ++ if not os.path.isdir(hba_full_path): ++ os.mkdir(hba_full_path) ++ ++ # create dev if it doesn't exist ++ full_path = tcm_full_path(dev_path) ++ if os.path.isdir(full_path): ++ tcm_err("TCM/ConfigFS storage object already exists: " + full_path) ++ else: ++ os.mkdir(full_path) ++ ++ # Determine if --establishdev is being called and we want to skip ++ # the T10 Unit Serial Number generation ++ gen_uuid = True ++ if establishdev: ++ gen_uuid = False ++ ++ # Calls into submodules depending on target_core_mod subsystem plugin ++ for tcm in tcm_types: ++ if hba_path.startswith(tcm["name"] + "_"): ++ try: ++ if tcm["module"]: ++ # modules expect plugin_params to be a list, for now. ++ tcm["module"].createvirtdev(dev_path, [plugin_params]) ++ else: ++ tcm_err("no module for %s" % tcm["name"]) ++ except: ++ os.rmdir(full_path) ++ print "Unable to register TCM/ConfigFS storage object: " \ ++ + full_path ++ raise ++ ++ print tcm_read(full_path + "/info") ++ ++ if tcm["gen_uuid"] and gen_uuid: ++ tcm_generate_uuid_for_unit_serial(dev_path) ++ tcm_alua_check_metadata_dir(dev_path) ++ break ++ ++def tcm_get_unit_serial(dev_path): ++ string = tcm_read(tcm_full_path(dev_path) + "/wwn/vpd_unit_serial") ++ return string.split(":")[1].strip() ++ ++def tcm_show_aptpl_metadata(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ aptpl_file = "/var/target/pr/aptpl_" + tcm_get_unit_serial(dev_path) ++ if not os.path.isfile(aptpl_file): ++ tcm_err("Unable to dump PR APTPL metadata file: " + aptpl_file) ++ ++ print tcm_read(aptpl_file) ++ ++def tcm_delete_aptpl_metadata(unit_serial): ++ aptpl_file = "/var/target/pr/aptpl_" + unit_serial ++ if not os.path.isfile(aptpl_file): ++ return ++ ++ shutil.rmtree(aptpl_file) ++ ++def tcm_process_aptpl_metadata(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ full_path = tcm_full_path(dev_path) ++ ++ aptpl_file = "/var/target/pr/aptpl_" + tcm_get_unit_serial(dev_path) ++ if not os.path.isfile(aptpl_file): ++ return ++ ++ # read PR info from file ++ lines = tcm_read(aptpl_file).split() ++ ++ if not lines[0].startswith("PR_REG_START:"): ++ return ++ ++ reservations = [] ++ for line in lines: ++ if line.startswith("PR_REG_START:"): ++ res_list = [] ++ elif line.startswith("PR_REG_END:"): ++ reservations.append(res_list) ++ else: ++ res_list.append(line.strip()) ++ ++ # write info into configfs ++ for res in reservations: ++ tcm_write(full_path + "/pr/res_aptpl_metadata", ",".join(res)) ++ ++def tcm_establishvirtdev(dev_path, plugin_params): ++ tcm_createvirtdev(dev_path, plugin_params, True) ++ ++def tcm_create_pscsi(dev_path, ctl): ++ # convert passed 3-tuple to format pscsi expects ++ # "1:3:5" -> "scsi_channel_id=1,scsi_target_id=3..." ++ # ++ param_names = ("scsi_channel_id", "scsi_target_id", "scsi_lun_id") ++ ++ pscsi_params = zip(param_names, ctl.split(":")) ++ pscsi_params_str = ",".join([x + "=" + y for x, y in pscsi_params]) ++ ++ tcm_createvirtdev(dev_path, pscsi_params_str) ++ ++def tcm_create_pscsibyudev(dev_path, udev_path): ++ tcm_createvirtdev(cfs_dev, udev_path) ++ ++def tcm_create_iblock(dev_path, udev_path): ++ tcm_createvirtdev(dev_path, udev_path) ++ ++def tcm_create_fileio(dev_path, filename, size): ++ fileio_params = "fd_dev_name=" + filename + ",fd_dev_size=" + size ++ tcm_createvirtdev(dev_path, fileio_params) ++ ++def tcm_create_ramdisk(dev_path, pages): ++ tcm_createvirtdev(dev_path, pages) ++ ++def __tcm_freevirtdev(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ full_path = tcm_full_path(dev_path) ++ ++ for tg_pt_gp in os.listdir(full_path + "/alua/"): ++ if tg_pt_gp == "default_tg_pt_gp": ++ continue ++ __tcm_del_alua_tgptgp(dev_path, tg_pt_gp) ++ ++ os.rmdir(full_path) ++ ++def tcm_freevirtdev(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ unit_serial = tcm_get_unit_serial(dev_path) ++ ++ __tcm_freevirtdev(dev_path) ++ # For explict tcm_node --freedev, delete any remaining ++ # PR APTPL and ALUA metadata ++ tcm_delete_aptpl_metadata(unit_serial) ++ tcm_alua_delete_metadata_dir(unit_serial) ++ ++def tcm_list_dev_attribs(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ full_path = tcm_full_path(dev_path) ++ ++ print "TCM Storage Object Attributes for " + full_path ++ for attrib in os.listdir(full_path + "/attrib/"): ++ print " %s: %s" % \ ++ (attrib, tcm_read(full_path + "/attrib/" + attrib).strip()) ++ ++def tcm_list_hbas(): ++ for hba in os.listdir(tcm_root): ++ if hba == "alua": ++ continue ++ ++ print "\------> " + hba ++ dev_root = tcm_root + "/" + hba ++ print "\t" + tcm_read(dev_root+"/hba_info").strip() ++ ++ for dev in os.listdir(dev_root): ++ if dev in ("hba_info", "hba_mode"): ++ continue ++ ++ try: ++ value = tcm_read(dev_root + "/" + dev + "/info") ++ except IOError, msg: ++ print " \-------> " + dev ++ print " No TCM object association active, skipping" ++ continue ++ ++ udev_path = tcm_read(dev_root + "/" + dev + "/udev_path") ++ if udev_path: ++ udev_str = "udev_path: " + udev_path.rstrip() ++ else: ++ udev_str = "udev_path: N/A" ++ ++ print " \-------> " + dev ++ print " " + value.rstrip() ++ print " " + udev_str ++ ++def tcm_list_alua_lugps(): ++ for lu_gp in os.listdir(tcm_root + "/alua/lu_gps"): ++ group_path = tcm_root + "/alua/lu_gps/" + lu_gp ++ lu_gp_id = tcm_read(group_path + "/lu_gp_id").strip() ++ print "\------> " + lu_gp + " LUN Group ID: " + lu_gp_id ++ ++ lu_gp_members = tcm_read(group_path + "/members").strip().split() ++ ++ if not lu_gp_members: ++ print " No Logical Unit Group Members" ++ continue ++ ++ for member in lu_gp_members: ++ print " " + member ++ ++def tcm_dump_alua_state(alua_state_no): ++ if alua_state_no == "0": ++ return "Active/Optimized" ++ elif alua_state_no == "1": ++ return "Active/NonOptimized" ++ elif alua_state_no == "2": ++ return "Standby" ++ elif alua_state_no == "3": ++ return "Unavailable" ++ elif alua_state_no == "15": ++ return "Transition" ++ else: ++ return "Unknown" ++ ++def tcm_list_alua_tgptgp(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ gp_path = tcm_full_path(dev_path) + "/alua/" + gp_name ++ ++ gp_id = tcm_read(gp_path + "/tg_pt_gp_id").strip() ++ print "\------> " + tg_pt_gp + " Target Port Group ID: " + tg_pt_gp_id ++ ++ alua_type = tcm_read(gp_path + "/alua_access_type").strip() ++ print " Active ALUA Access Type(s): " + alua_type ++ ++ alua_state = tcm_read(gp_path + "/alua_access_state").strip() ++ print " Primary Access State: " + tcm_dump_alua_state(alua_state) ++ ++ try: ++ access_status = tcm_read(gp_path + "/alua_access_status").strip() ++ print " Primary Access Status: " + access_status ++ except IOError: ++ pass ++ ++ preferred = tcm_read(gp_path + "/preferred").strip() ++ print " Preferred Bit: " + preferred ++ ++ nonop_delay = tcm_read(gp_path + "/nonop_delay_msecs").strip() ++ print " Active/NonOptimized Delay in milliseconds: " + nonop_delay ++ ++ trans_delay = tcm_read(gp_path + "/trans_delay_msecs").strip() ++ print " Transition Delay in milliseconds: " + trans_delay ++ ++ gp_members = tcm_read(gp_path + "/members").strip().split() ++ ++ print " \------> TG Port Group Members" ++ if not gp_members: ++ print " No Target Port Group Members" ++ else: ++ for member in gp_members: ++ print " " + member ++ ++def tcm_list_alua_tgptgps(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ for tg_pt_gp in os.listdir(tcm_full_path(dev_path) + "/alua/"): ++ tcm_list_alua_tgptgp(dev_path, tg_pt_gp) ++ ++def tcm_show_persistent_reserve_info(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ full_path = tcm_full_path(dev_path) ++ ++ for f in os.listdir(full_path + "/pr/"): ++ info = tcm_read(full_path + "/pr/" + f).strip() ++ if info: ++ print info ++ ++def tcm_set_alua_state(dev_path, gp_name, access_state): ++ tcm_check_dev_exists(dev_path) ++ ++ new_alua_state_str = str(access_state).lower() ++ ++ if new_alua_state_str == "o": ++ alua_state = 0 # Active/Optimized ++ elif new_alua_state_str == "a": ++ alua_state = 1 # Active/NonOptimized ++ elif new_alua_state_str == "s": ++ alua_state = 2 # Standby ++ elif new_alua_state_str == "u": ++ alua_state = 3 # Unavailable ++ else: ++ tcm_err("Unknown ALUA access state: " + new_alua_state_str) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/" + gp_name + "/alua_access_state" ++ tcm_write(alua_path, str(alua_state)) ++ ++def tcm_set_alua_type(dev_path, gp_name, access_type): ++ tcm_check_dev_exists(dev_path) ++ ++ new_alua_type_str = str(access_type).lower() ++ ++ if new_alua_type_str == "both": ++ alua_type = 3 ++ elif new_alua_type_str == "explict": ++ alua_type = 2 ++ elif new_alua_type_str == "implict": ++ alua_type = 1 ++ elif new_alua_type_str == "none": ++ alua_type = 0 ++ else: ++ tcm_err("Unknown ALUA access type: " + new_alua_type_str) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/" + gp_name + "/alua_access_type" ++ tcm_write(alua_path, str(alua_type)) ++ ++def tcm_set_alua_nonop_delay(dev_path, gp_name, msec_delay): ++ tcm_check_dev_exists(dev_path) ++ ++ if not os.path.isdir(tcm_full_path(dev_path) + "/alua/" + gp_name): ++ tcm_err("Unable to locate TG Pt Group: " + gp_name) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/" + gp_name + "/nonop_delay_msecs" ++ tcm_write(alua_path, str(msec_delay)) ++ ++def tcm_set_alua_trans_delay(dev_path, gp_name, msec_delay): ++ tcm_check_dev_exists(dev_path) ++ ++ if not os.path.isdir(tcm_full_path(dev_path) + "/alua/" + gp_name): ++ tcm_err("Unable to locate TG Pt Group: " + gp_name) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/" + gp_name + "/trans_delay_msecs" ++ tcm_write(alua_path, str(msec_delay)) ++ ++def tcm_clear_alua_tgpt_pref(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ if not os.path.isdir(tcm_full_path(dev_path) + "/alua/" + gp_name): ++ tcm_err("Unable to locate TG Pt Group: " + gp_name) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/" + gp_name + "/preferred" ++ tcm_write(alua_path, "0") ++ ++def tcm_set_alua_tgpt_pref(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ if not os.path.isdir(tcm_full_path(dev_path) + "/alua/" + gp_name): ++ tcm_err("Unable to locate TG Pt Group: " + gp_name) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/" + gp_name + "/preferred" ++ tcm_write(alua_path, "1") ++ ++def tcm_set_alua_lugp(dev_path, gp_name): ++ tcm_check_dev_exists(dev_path) ++ ++ if not os.path.isdir(tcm_full_path(dev_path) + "/alua/lu_gps/" + gp_name): ++ tcm_err("Unable to locate ALUA Logical Unit Group: " + gp_name) ++ ++ alua_path = tcm_full_path(dev_path) + "/alua/lu_gps/" + gp_name + "/alua_lu_gp" ++ tcm_write(alua_path, gp_name) ++ ++def tcm_set_dev_attrib(dev_path, attrib, value): ++ tcm_check_dev_exists(dev_path) ++ ++ tcm_write(tcm_full_path(dev_path) + "/attrib/" + attrib, value) ++ ++def tcm_set_udev_path(dev_path, udev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ tcm_write(tcm_full_path(dev_path) + "/udev_path", udev_path, newline=False) ++ ++def tcm_set_wwn_unit_serial(dev_path, unit_serial): ++ tcm_check_dev_exists(dev_path) ++ ++ tcm_write(tcm_full_path(dev_path) + "/wwn/vpd_unit_serial", unit_serial) ++ ++def tcm_set_wwn_unit_serial_with_md(dev_path, unit_serial): ++ tcm_check_dev_exists(dev_path) ++ ++ tcm_set_wwn_unit_serial(dev_path, unit_serial) ++ # Process PR APTPL metadata ++ tcm_process_aptpl_metadata(dev_path) ++ # Make sure the ALUA metadata directory exists for this storage object ++ tcm_alua_check_metadata_dir(dev_path) ++ ++def tcm_show_udev_path(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ print tcm_read(tcm_full_path(dev_path) + "/udev_path") ++ ++def tcm_show_wwn_info(dev_path): ++ tcm_check_dev_exists(dev_path) ++ ++ full_path = tcm_full_path(dev_path) + "/wwn/" ++ ++ for f in os.listdir(full_path): ++ info = tcm_read(full_path + f).strip() ++ if info: ++ print info ++ ++def tcm_unload(): ++ if not os.path.isdir(tcm_root): ++ tcm_err("Unable to access tcm_root: " + tcm_root) ++ ++ hba_root = os.listdir(tcm_root) ++ ++ for f in hba_root: ++ if f == "alua": ++ continue ++ ++ tcm_delhba(f) ++ ++ for lu_gp in os.listdir(tcm_root + "/alua/lu_gps"): ++ if lu_gp == "default_lu_gp": ++ continue ++ ++ tcm_del_alua_lugp(lu_gp) ++ ++ # Unload TCM subsystem plugin modules ++ for module in ("iblock", "file", "pscsi", "stgt"): ++ os.system("rmmod target_core_%s" % module) ++ ++ # Unload TCM Core ++ rmmod_op = "rmmod target_core_mod" ++ ret = os.system(rmmod_op) ++ if ret: ++ tcm_err("Unable to rmmod target_core_mod") ++ ++def tcm_version(): ++ return tcm_read("/sys/kernel/config/target/version").strip() ++ ++cmdline_options = ( \ ++ dict(opt_str="--addlungp", callback=tcm_add_alua_lugp, nargs=1, ++ dest="lu_gp_name", help="Add ALUA Logical Unit Group"), ++ dict(opt_str=("--addtgptgp","--addaluatpg"), ++ callback=tcm_add_alua_tgptgp, nargs=2, ++ dest="HBA/DEV ", ++ help="Add ALUA Target Port Group to Storage Object"), ++ dict(opt_str=("--addtgptgpwithmd","--addaluatpgwithmd"), action="callback", ++ callback=tcm_add_alua_tgptgp_with_md, nargs=3, ++ dest="HBA/DEV ", ++ help="Add ALUA Target Port Group to Storage Object with ID and process ALUA metadata"), ++ dict(opt_str=("--block","--iblock"), callback=tcm_create_iblock, nargs=2, ++ dest="HBA/DEV ", ++ help="Associate TCM/IBLOCK object with Linux/BLOCK device"), ++ dict(opt_str="--clearaluapref", callback=tcm_clear_alua_tgpt_pref, ++ nargs=2, dest="HBA/DEV ", ++ help="Clear ALUA Target Port Group Preferred Bit"), ++ dict(opt_str="--delhba", callback=tcm_delhba, nargs=1, ++ dest="HBA", help="Delete TCM Host Bus Adapter (HBA)"), ++ dict(opt_str="--dellungp", callback=tcm_del_alua_lugp, nargs=1, ++ dest="lu_gp_name", help="Delete ALUA Logical Unit Group"), ++ dict(opt_str=("--deltgptgp","--delaluatpg"), callback=tcm_del_alua_tgptgp, nargs=2, ++ dest="HBA/DEV TG_PT_GP_NAME", ++ help="Delete ALUA Target Port Group from Storage Object"), ++ dict(opt_str="--createdev", callback=tcm_createvirtdev, nargs=2, ++ dest="HBA/DEV ", ++ help="Create TCM Storage Object using subsystem dependent parameters," ++ " and generate new T10 Unit Serial for IBLOCK,FILEIO,RAMDISK"), ++ dict(opt_str="--establishdev", callback=tcm_establishvirtdev, nargs=2, ++ dest="HBA/DEV ", ++ help="Create TCM Storage Object using subsystem dependent parameters, do" ++ "not generate new T10 Unit Serial"), ++ dict(opt_str="--fileio", callback=tcm_create_fileio, nargs=3, ++ dest="HBA/DEV ", ++ help="Associate TCM/FILEIO object with Linux/VFS file or underlying" ++ " device for buffered FILEIO"), ++ dict(opt_str="--freedev", callback=tcm_freevirtdev, nargs=1, ++ dest="HBA/DEV", help="Free TCM Storage Object"), ++ dict(opt_str="--listdevattr", callback=tcm_list_dev_attribs, nargs=1, ++ dest="HBA/DEV", help="List TCM storage object device attributes"), ++ dict(opt_str="--listhbas", callback=tcm_list_hbas, nargs=0, ++ help="List TCM Host Bus Adapters (HBAs)"), ++ dict(opt_str="--listlugps", callback=tcm_list_alua_lugps, nargs=0, ++ help="List ALUA Logical Unit Groups"), ++ dict(opt_str=("--listtgptgp","--listaluatpg"), callback=tcm_list_alua_tgptgp, nargs=2, ++ dest="HBA/DEV ", ++ help="List specific ALUA Target Port Group for Storage Object"), ++ dict(opt_str=("--listtgptgps","--listaluatpgs"), callback=tcm_list_alua_tgptgps, nargs=1, ++ dest="HBA/DEV", help="List all ALUA Target Port Groups for Storage Object"), ++ dict(opt_str="--pr", callback=tcm_show_persistent_reserve_info, nargs=1, ++ dest="HBA/DEV", help="Show Persistent Reservation info"), ++ dict(opt_str="--praptpl", callback=tcm_process_aptpl_metadata, nargs=1, ++ dest="HBA/DEV", help="Process PR APTPL metadata from file"), ++ dict(opt_str="--prshowmd", callback=tcm_show_aptpl_metadata, nargs=1, ++ dest="HBA/DEV", help="Show APTPL metadata file"), ++ dict(opt_str="--ramdisk", callback=tcm_create_ramdisk, nargs=2, ++ dest="HBA/DEV ", help="Create and associate TCM/RAMDISK object"), ++ dict(opt_str=("--scsi","--pscsi"), callback=tcm_create_pscsi, nargs=2, ++ dest="HBA/DEV ", ++ help="Associate TCM/pSCSI object with Linux/SCSI device by bus location"), ++ dict(opt_str=("--scsibyudev", "--pscsibyudev"), callback=tcm_create_pscsibyudev, nargs=2, ++ dest="HBA/DEV ", ++ help="Associate TCM/pSCSI object with Linux/SCSI device by UDEV Path"), ++ dict(opt_str="--setaluadelay", callback=tcm_set_alua_nonop_delay, nargs=3, ++ dest="HBA/DEV ", ++ help="Set ALUA Target Port Group delay for Active/NonOptimized in milliseconds"), ++ dict(opt_str="--setaluapref", callback=tcm_set_alua_tgpt_pref, nargs=2, ++ dest="HBA/DEV ", help="Set ALUA Target Port Group Preferred Bit"), ++ dict(opt_str="--setaluastate", callback=tcm_set_alua_state, nargs=3, ++ dest="HBA/DEV ", ++ help="Set ALUA access state for TG_PT_GP_NAME on Storage Object. The value access" ++ " states are \"o\" = active/optimized, \"a\" = active/nonoptimized, \"s\" = standby," ++ " \"u\" = unavailable"), ++ dict(opt_str="--setaluatransdelay", callback=tcm_set_alua_trans_delay, nargs=3, ++ dest="HBA/DEV ", ++ help="Set ALUA Target Port Group Transition delay"), ++ dict(opt_str="--setaluatype", callback=tcm_set_alua_type, nargs=3, ++ dest="HBA/DEV ", ++ help="Set ALUA access type for TG_PT_GP_NAME on Storage Object. The value type" ++ " states are \"both\" = implict/explict, \"explict\", \"implict\", or \"none\""), ++ dict(opt_str="--setdevattr", callback=tcm_set_dev_attrib, nargs=3, ++ dest="HBA/DEV ", ++ help="Set new value for TCM storage object device attribute"), ++ dict(opt_str="--setlugp", callback=tcm_set_alua_lugp, nargs=2, ++ dest="HBA/DEV LU_GP_NAME", help="Set ALUA Logical Unit Group"), ++ dict(opt_str="--setudevpath", callback=tcm_set_udev_path, nargs=2, ++ dest="HBA/DEV ", ++ help="Set UDEV Path Information, only used when --createdev did not contain" ++ " as parameter"), ++ dict(opt_str="--setunitserial", callback=tcm_set_wwn_unit_serial, nargs=2, ++ dest="HBA/DEV ", help="Set T10 EVPD Unit Serial Information"), ++ dict(opt_str="--setunitserialwithmd", callback=tcm_set_wwn_unit_serial_with_md, nargs=2, ++ dest="HBA/DEV ", ++ help="Set T10 EVPD Unit Serial Information and process PR APTPL metadata"), ++ dict(opt_str="--udevpath", callback=tcm_show_udev_path, nargs=1, ++ dest="HBA/DEV", help="Show UDEV Path Information for TCM storage object"), ++ dict(opt_str="--unload", callback=tcm_unload, nargs=0, ++ help="Unload target_core_mod"), ++ dict(opt_str="--wwn", callback=tcm_show_wwn_info, nargs=1, ++ dest="HBA/DEV", help="Show WWN info"), ++) ++ ++def dispatcher(option, opt_str, value, parser, orig_callback): ++ if option.nargs == 1: ++ value = (value,) ++ value = [str(x).strip() for x in value] ++ orig_callback(*value) ++ ++def main(): ++ ++ parser = OptionParser(version=tcm_version()) ++ ++ for opt in cmdline_options: ++ # cmd_aliases can be string or tuple of strings. ++ # we're unpacking below, so convert strings to 1 item tuples ++ cmd_aliases = opt["opt_str"] ++ if isinstance(cmd_aliases, basestring): ++ cmd_aliases = (cmd_aliases,) ++ del opt["opt_str"] ++ # common params for all options ++ opt["action"] = "callback" ++ opt["type"] = "string" ++ opt["callback_kwargs"] = dict(orig_callback=opt["callback"]) ++ opt["callback"] = dispatcher ++ parser.add_option(*cmd_aliases, **opt) ++ ++ (options, args) = parser.parse_args() ++ if len(sys.argv) == 1: ++ parser.print_help() ++ sys.exit(0) ++ elif not re.search('--', sys.argv[1]): ++ tcm_err("Unknown CLI option: " + sys.argv[1]) ++ ++if __name__ == "__main__": ++ main() +diff --git a/targetcli/tcm_pscsi.py b/targetcli/tcm_pscsi.py +new file mode 100644 +index 0000000..f849279 +--- /dev/null ++++ b/targetcli/tcm_pscsi.py +@@ -0,0 +1,184 @@ ++import os ++import subprocess as sub ++import string, re ++from optparse import OptionParser ++ ++tcm_root = "/sys/kernel/config/target/core" ++ ++def print_lsscsi(option, opt_str, value, parser): ++ command = "lsscsi" ++ ++ p = sub.Popen(command,shell=True, stdout=sub.PIPE).stdout ++ while 1: ++ line = p.readline() ++ if not line: break ++ print line, ++ ++def pscsi_get_hba_prefix(arg): ++ path = "/sys/kernel/config/target/core/pscsi_" + arg ++ return path ++ ++def pscsi_scan_lsscsi(option, opt_str, value, parser): ++ command = "lsscsi -H" ++ p = sub.Popen(command,shell=True, stdout=sub.PIPE).stdout ++ while 1: ++ line = p.readline() ++ if not line: break ++ line.split() ++ host_id = line[1] ++ print "SCSI Host ID: " + host_id ++ ++ cfs_path = pscsi_get_hba_prefix(host_id) ++ if (os.path.isdir(cfs_path)): ++ print "pSCSI HBA already registered, skipping" ++ continue ++ ++ print cfs_path ++ ret = os.mkdir(cfs_path) ++ print "os.path.mkdir ret: " + str(ret) ++ if not ret: ++ print "Successfully added ConfigFS path " + cfs_path ++ ++def createvirtdev(path, params): ++ ++# print "Calling pscsi createvirtdev: path " + path ++ cfs_path = tcm_root + "/" + path + "/" ++ ++# print "Calling pscsi createvirtdev: params " + str(params) ++ pscsi_params = params[0] ++# print pscsi_params ++ ++ # Exract HCTL from sysfs and set udev_path ++ if re.search('/dev/', pscsi_params): ++ udev_path = pscsi_params.rstrip() ++ if re.search('/dev/disk/', udev_path): ++ udev_op = "/bin/ls -l " + udev_path ++ p = sub.Popen(udev_op, shell=True, stdout=sub.PIPE).stdout ++ if not p: ++ print "pSCSI: Unable to locate scsi_device from udev_path: " + udev_path ++ return -1 ++ ++ line = p.readline() ++ out = line.split(' ../../'); ++ p.close() ++ if not out: ++ print "pSCSI: Unable to locate scsi_device from udev_path: " + udev_path ++ return -1 ++ ++ scsi_dev = out[1].rstrip() ++ elif re.search('/dev/s', udev_path): ++ out = udev_path.split('/dev/') ++ scsi_dev = out[1] ++ else: ++ print "pSCSI: Unable to locate scsi_device from udev_path: " + udev_path ++ return -1 ++ ++ # Convert scdX to sr0 for TYPE_ROM in /sys/block/ ++ if re.search('scd', scsi_dev): ++ scsi_dev = scsi_dev.replace('scd', 'sr'); ++ ++ if not os.path.isdir("/sys/block/" + scsi_dev + "/device/"): ++ print "pSCSI: Unable to locate scsi_device from udev_path: " + udev_path ++ return -1 ++ ++ scsi_dev_sysfs = "/sys/block/" + scsi_dev + "/device" ++ udev_op = "/bin/ls -l " + scsi_dev_sysfs ++ p = sub.Popen(udev_op, shell=True, stdout=sub.PIPE).stdout ++ if not p: ++ print "pSCSI: Unable to locate scsi_device from udev_path: " + udev_path ++ return -1 ++ ++ line = p.readline() ++ out = line.split('/') ++ p.close() ++ ++ scsi_hctl_tmp = out[len(out)-1] ++ scsi_hctl = scsi_hctl_tmp.split(':') ++ scsi_host_id = scsi_hctl[0] ++ scsi_channel_id = scsi_hctl[1] ++ scsi_target_id = scsi_hctl[2] ++ scsi_lun_id = scsi_hctl[3] ++ print "pSCSI: Referencing HCTL " + out[1].rstrip() + " for udev_path: " + udev_path ++ ++ set_udev_path_op = "echo -n " + udev_path + " > " + cfs_path + "udev_path" ++ ret = os.system(set_udev_path_op) ++ if ret: ++ print "pSCSI: Unable to set udev_path in " + cfs_path + " for: " + udev_path ++ return -1 ++ ++ pscsi_params = "scsi_host_id=" + scsi_host_id + ",scsi_channel_id=" + scsi_channel_id + ",scsi_target_id=" + scsi_target_id + ",scsi_lun_id=" + scsi_lun_id.rstrip() ++ ++ ++ control_opt = "echo -n " + pscsi_params + " > " + cfs_path + "control" ++# print "Calling control_opt " + control_opt ++ ret = os.system(control_opt) ++ if ret: ++ print "pSCSI: createvirtdev failed for control_opt with " + pscsi_params ++ return -1 ++ ++ enable_opt = "echo 1 > " + cfs_path + "enable" ++# print "Calling enable_opt " + enable_opt ++ ret = os.system(enable_opt) ++ if ret: ++ print "pSCSI: createvirtdev failed for enable_opt with " + pscsi_params ++ return -1 ++ ++def pscsi_freevirtdev(): ++ pass ++ ++def pscsi_get_params(path): ++ # Reference by udev_path if available ++ udev_path_file = path + "/udev_path" ++ p = os.open(udev_path_file, 0) ++ value = os.read(p, 1024) ++ if re.search('/dev/', value): ++ os.close(p) ++ return value.rstrip() ++ ++ os.close(p) ++ ++ info_file = path + "/info" ++ p = open(info_file, 'rU') ++ try: ++ value = p.read(1024) ++ except IOError, msg: ++ p.close() ++ return ++ p.close() ++ ++ off = value.index('Channel ID: ') ++ off += 12 ++ channel_id_tmp = value[off:] ++ channel_id = channel_id_tmp.split(' ') ++ off = value.index('Target ID: ') ++ off += 11 ++ target_id_tmp = value[off:] ++ target_id = target_id_tmp.split(' ') ++ off = value.index('LUN: ') ++ off += 5 ++ lun_id_tmp = value[off:] ++ lun_id = lun_id_tmp.split(' ') ++ params = "" ++ ++ try: ++ off = value.index('Host ID: ') ++ except ValueError: ++ params = "" ++ else: ++ off += 9 ++ host_id_tmp = value[off:] ++ host_id = host_id_tmp.split(' ') ++ host_id = host_id[0].rstrip() ++ if host_id != "PHBA": ++ params += "scsi_host_id=" + host_id[0] + "," ++ ++ params += "scsi_channel_id=" + channel_id[0] + ",scsi_target_id=" + target_id[0] + ",scsi_lun_id=" + lun_id[0].rstrip() ++ ++ # scsi_channel_id=, scsi_target_id= and scsi_lun_id= reference for tcm_node --createdev ++ return params ++ ++#parser = OptionParser() ++#parser.add_option("-s", "--scan", action="callback", callback=pscsi_scan_lsscsi, ++# default=False, help="Scan and register pSCSI HBAs with TCM/ConfigFS") ++#parser.parse_args() ++# +diff --git a/targetcli/tcm_ramdisk.py b/targetcli/tcm_ramdisk.py +new file mode 100644 +index 0000000..10b4a5f +--- /dev/null ++++ b/targetcli/tcm_ramdisk.py +@@ -0,0 +1,53 @@ ++import os ++import subprocess as sub ++import string, re ++from optparse import OptionParser ++ ++tcm_root = "/sys/kernel/config/target/core" ++ ++def createvirtdev(path, params): ++ ++# print "Calling ramdisk createvirtdev: path " + path ++ cfs_path = tcm_root + "/" + path + "/" ++# print "Calling ramdisk createvirtdev: params " + str(params) ++ rd_pages = params[0] ++ ++ rd_params = "rd_pages=" + rd_pages ++# print "rd_params: " + rd_params ++ ++ control_opt = "echo -n " + rd_params.rstrip() + " > " + cfs_path + "/control" ++# print "control_opt: " + control_opt ++ ret = os.system(control_opt) ++ if ret: ++ print "RAMDISK: createvirtdev failed for control_opt with " + rd_params ++ return -1 ++ ++ enable_opt = "echo 1 > " + cfs_path + "enable" ++# print "Calling enable_opt " + enable_opt ++ ret = os.system(enable_opt) ++ if ret: ++ print "RAMDISK: createvirtdev failed for enable_opt with " + rd_params ++ return -1 ++ ++def rd_freevirtdev(): ++ pass ++ ++def rd_get_params(path): ++ ++ info_file = path + "/info" ++ p = open(info_file, 'rU') ++ try: ++ value = p.read(1024) ++ except IOError, msg: ++ p.close() ++ return ++ p.close() ++ ++ off = value.index('PAGE_SIZE: ') ++ off += 11 # Skip over "PAGE_SIZE: " ++ rd_pages_tmp = value[off:] ++ rd_pages = rd_pages_tmp.split('*') ++ params = "rd_pages=" + rd_pages[0] ++ ++ # rd_pages= parameter for tcm_node --createdev ++ return rd_pages[0] +-- +1.7.1 + diff --git a/0003-Hack.-dump-scripts-aren-t-in-PATH-anymore-so-call-th.patch b/0003-Hack.-dump-scripts-aren-t-in-PATH-anymore-so-call-th.patch new file mode 100644 index 0000000..39dc87e --- /dev/null +++ b/0003-Hack.-dump-scripts-aren-t-in-PATH-anymore-so-call-th.patch @@ -0,0 +1,136 @@ +From 7b4dce12237dc9b79dbe4f2ac9dbbb125d314b2c Mon Sep 17 00:00:00 2001 +From: Andy Grover +Date: Sat, 30 Jul 2011 18:31:45 -0700 +Subject: [PATCH 3/6] Hack. dump scripts aren't in PATH anymore, so call them explicitly. + +Signed-off-by: Andy Grover +--- + targetcli/lio_dump.py | 3 ++- + targetcli/tcm_dump.py | 22 +++++++++++++--------- + targetcli/tcm_fabric.py | 4 ++-- + 3 files changed, 17 insertions(+), 12 deletions(-) + +diff --git a/targetcli/lio_dump.py b/targetcli/lio_dump.py +index 81c5104..6ce217a 100644 +--- a/targetcli/lio_dump.py ++++ b/targetcli/lio_dump.py +@@ -221,7 +221,8 @@ def lio_backup_to_file(option, opt_str, value, parser): + print "Unable to open backup_dir" + sys.exit(1) + +- op = "lio_dump --stdout" ++ prefix = "python /usr/lib/python2.6/site-packages/rtsadmin/" ++ op = prefix + "lio_dump.py --stdout" + p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout + if not p: + print "Unable to dump LIO-Target/ConfigFS running state" +diff --git a/targetcli/tcm_dump.py b/targetcli/tcm_dump.py +index bf80632..1b76b62 100644 +--- a/targetcli/tcm_dump.py ++++ b/targetcli/tcm_dump.py +@@ -20,6 +20,10 @@ tcm_root = "/sys/kernel/config/target/core" + def tcm_dump_hba_devices(): + pass + ++path_prefix = "python /usr/lib/python2.6/site-packages/rtsadmin/" ++tcm_node_path = path_prefix + "tcm_node.py" ++tcm_dump_path = path_prefix + "tcm_dump.py" ++ + def tcm_dump_configfs(option, opt_str, value, parser): + + if not os.path.isdir(tcm_root): +@@ -68,35 +72,35 @@ def tcm_dump_configfs(option, opt_str, value, parser): + params = tcm_pscsi.pscsi_get_params(dev) + if not params: + continue +- print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ print tcm_node_path + " --establishdev " + f + "/" + g + " " + str(params) + result = re.search('iblock_', f) + if result: + dev = dev_root + g + params = tcm_iblock.iblock_get_params(dev) + if not params: + continue +- print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ print tcm_node_path + " --establishdev " + f + "/" + g + " " + str(params) + result = re.search('rd_dr_', f) + if result: + dev = dev_root + g + params = tcm_ramdisk.rd_get_params(dev) + if not params: + continue +- print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ print tcm_node_path + " --establishdev " + f + "/" + g + " " + str(params) + result = re.search('rd_mcp_', f) + if result: + dev = dev_root + g + params = tcm_ramdisk.rd_get_params(dev) + if not params: + continue +- print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ print tcm_node_path + " --establishdev " + f + "/" + g + " " + str(params) + result = re.search('fileio_', f) + if result: + dev = dev_root + g + params = tcm_fileio.fd_get_params(dev) + if not params: + continue +- print "tcm_node --establishdev " + f + "/" + g + " " + str(params) ++ print tcm_node_path + " --establishdev " + f + "/" + g + " " + str(params) + + # Dump T10 VP Unit Serial for all non Target_Core_Mod/pSCSI objects + result = re.search('pscsi_', f) +@@ -108,7 +112,7 @@ def tcm_dump_configfs(option, opt_str, value, parser): + off += 8 # Skip over "Number: " + unit_serial = value[off:] + # Note that this will handle read, parse and set any PR APTPL metadata +- print "tcm_node --setunitserialwithmd " + f + "/" + g + " " + unit_serial.rstrip() ++ print tcm_node_path + " --setunitserialwithmd " + f + "/" + g + " " + unit_serial.rstrip() + os.close(p) + + # Dump device object alias +@@ -146,7 +150,7 @@ def tcm_dump_configfs(option, opt_str, value, parser): + os.close(p) + if not value: + continue +- print "tcm_node --addaluatpgwithmd " + f + "/" + g + " " + tg_pt_gp + " " + value.rstrip() ++ print tcm_node_path + " --addaluatpgwithmd " + f + "/" + g + " " + tg_pt_gp + " " + value.rstrip() + # Dump the ALUA types + tg_pt_gp_type_file = dev_root + g + "/alua/" + tg_pt_gp + "/alua_access_type" + p = os.open(tg_pt_gp_type_file, 0) +@@ -244,7 +248,7 @@ def tcm_dump_configfs(option, opt_str, value, parser): + print "echo " + attr_val + " > " + attrib_file + + if snap_enabled == 1: +- print "tcm_node --lvsnapstart " + f + "/" + g ++ print tcm_node_path + " --lvsnapstart " + f + "/" + g + + def tcm_backup_to_file(option, opt_str, value, parser): + datetime = str(value) +@@ -261,7 +265,7 @@ def tcm_backup_to_file(option, opt_str, value, parser): + print "Unable to open backup_dir" + sys.exit(1) + +- op = "tcm_dump --stdout" ++ op = tcm_dump_path + " --stdout" + p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout + if not p: + print "Unable to dump Target_Core_Mod/ConfigFS running state" +diff --git a/targetcli/tcm_fabric.py b/targetcli/tcm_fabric.py +index 8a843d4..03f5570 100644 +--- a/targetcli/tcm_fabric.py ++++ b/targetcli/tcm_fabric.py +@@ -254,8 +254,8 @@ def fabric_backup_to_file(date_time, fabric_name, fabric_root, module_name): + if ret: + print "Unable to open backup_dir" + sys.exit(1) +- +- op = "tcm_fabric --stdout --fabric-name=" + fabric_name + " --fabric-root=" + fabric_root + " --module-name=" + module_name ++ prefix = "python /usr/lib/python2.6/site-packages/rtsadmin/" ++ op = prefix + "tcm_fabric.py --stdout --fabric-name=" + fabric_name + " --fabric-root=" + fabric_root + " --module-name=" + module_name + # print "Using op: " + op + p = sub.Popen(op, shell=True, stdout=sub.PIPE).stdout + if not p: +-- +1.7.1 + diff --git a/0004-ignore-errors-from-failure-to-set-device-attributes.patch b/0004-ignore-errors-from-failure-to-set-device-attributes.patch new file mode 100644 index 0000000..5a409ee --- /dev/null +++ b/0004-ignore-errors-from-failure-to-set-device-attributes.patch @@ -0,0 +1,26 @@ +From 8f6fb804f7bf68bafa4799fb821882bd97e777c8 Mon Sep 17 00:00:00 2001 +From: Andy Grover +Date: Mon, 1 Aug 2011 14:40:12 -0700 +Subject: [PATCH 4/6] ignore errors from failure to set device attributes + +Signed-off-by: Andy Grover +--- + targetcli/tcm_dump.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/targetcli/tcm_dump.py b/targetcli/tcm_dump.py +index 1b76b62..abfd592 100644 +--- a/targetcli/tcm_dump.py ++++ b/targetcli/tcm_dump.py +@@ -211,7 +211,7 @@ def tcm_dump_configfs(option, opt_str, value, parser): + attrib_file = dev_attrib_root + h + p = os.open(attrib_file, 0) + value = os.read(p, 8) +- print "echo " + value.rstrip() + " > " + attrib_file ++ print "echo " + value.rstrip() + " > " + attrib_file + " 2>/dev/null" + os.close(p) + + # Dump snapshot attributes +-- +1.7.1 + diff --git a/0005-fix-spec_root-path.patch b/0005-fix-spec_root-path.patch new file mode 100644 index 0000000..0ae75fa --- /dev/null +++ b/0005-fix-spec_root-path.patch @@ -0,0 +1,29 @@ +From 8f91eb2b471f8db02888e80d37f90b1c6c42605a Mon Sep 17 00:00:00 2001 +From: Andy Grover +Date: Thu, 25 Aug 2011 14:41:29 -0700 +Subject: [PATCH 5/6] fix spec_root path + +We moved the specs from /var/target/fabric to /var/lib/target/fabric, so +this needs to be updated so saveconfig will work. + +Signed-off-by: Andy Grover +--- + targetcli/tcm_fabric.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/targetcli/tcm_fabric.py b/targetcli/tcm_fabric.py +index 03f5570..2c4c6dc 100644 +--- a/targetcli/tcm_fabric.py ++++ b/targetcli/tcm_fabric.py +@@ -6,7 +6,7 @@ import datetime, time + import optparse + + target_root = "/sys/kernel/config/target/" +-spec_root = "/var/target/fabric/" ++spec_root = "/var/lib/target/fabric/" + + def fabric_configfs_dump(fabric_name, fabric_root, module_name): + +-- +1.7.1 + diff --git a/0006-add-docs.patch b/0006-add-docs.patch new file mode 100644 index 0000000..0f556af --- /dev/null +++ b/0006-add-docs.patch @@ -0,0 +1,174 @@ +From 705561247e8397dd5c1c33269ad6f7c79c639713 Mon Sep 17 00:00:00 2001 +From: Andy Grover +Date: Tue, 20 Sep 2011 11:27:59 -0700 +Subject: [PATCH 6/6] add docs + +add a man page. + +Signed-off-by: Andy Grover +--- + targetcli.8 | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 152 insertions(+), 0 deletions(-) + create mode 100644 targetcli.8 + +diff --git a/targetcli.8 b/targetcli.8 +new file mode 100644 +index 0000000..d216ca0 +--- /dev/null ++++ b/targetcli.8 +@@ -0,0 +1,152 @@ ++.TH targetcli 8 ++.SH NAME ++.B targetcli ++.SH DESCRIPTION ++.B targetcli ++is a shell for viewing, editing, and saving the configuration of ++the kernel's target subsystem, also known as TCM/LIO. It enables the ++administrator to assign local storage resources backed by either files, ++volumes, or local SCSI devices, and export them to remote systems via ++network fabrics, such as iSCSI, FCoE, or others. ++.P ++The configuration layout is tree-based, similar to a filesystem, and ++navigated in a similar manner. ++.SH USAGE ++Invoke ++.B targetcli ++as root to enter the configuration shell. Use ++.B ls ++to list nodes below the current path. Configuration changes are made ++immediately. To retain changes on reboot, use ++.BR saveconfig . ++Moving ++around the tree is accomplished by the ++.B cd ++command, or by entering ++the new location directly. Use ++.B "help " ++for additional usage ++information. Tab-completion is enabled for commands and command ++arguments. ++.SH EXAMPLES ++To export a storage resource, 1) define a storage object using ++backstore, then 2) export the object via the FCoE network fabric. ++.SS DEFINING A STORAGE OBJECT WITH BACKSTORE ++.B backstores/fileio0 create disk1 /disks/disk1.img 140M ++.br ++Creates a storage object named ++.I disk1 ++with the given path and size. ++.B targetcli ++supports common size abbreviations like 'M', 'G', and 'T'. ++.P ++In addition to the ++.I fileio ++backstore for file-backed volumes, other backstore types include ++.I iblock ++for block-device-backed volumes, and ++.I pscsi ++for volumes backed by local SCSI devices. Please see the built-in help ++for more details on required parameters. ++.SS EXPORTING A STORAGE OBJECT VIA FCOE ++.B tcm_fc/ create 20:00:00:19:99:a8:34:bc ++.br ++Create an FCoE target with the given WWN. ++.B targetcli ++can tab-complete the WWN based on registered FCoE interfaces. If none ++are found, verify that they are properly configured and are shown in ++the output of ++.BR "fcoeadm -i" . ++.P ++.B tcm_fc/20:00:00:19:99:a8:34:bc/ ++.br ++If ++.B auto_cd_after_create ++is set to false, change to the configuration node for the given ++target, equivalent to giving the command prefixed by ++.BR cd . ++.P ++.B luns/ create /backstores/fileio/disk1 ++.br ++Create a new LUN for the interface, attached to a previously defined ++storage object. The storage object now shows up under the /backstores ++configuration node as ++.BR activated . ++.P ++.B acls/ create 00:99:88:77:66:55:44:33 ++.br ++Create an ACL (access control list), for defining the resources each ++initiator may access. The default behavior is to auto-map existing ++LUNs to the ACL; please see help for more information. ++.P ++The LUN should now be accessible via FCoE. ++.ig ++.SS EXPORTING A STORAGE OBJECT VIA ISCSI ++.B iscsi/ create ++.br ++Creates an iSCSI target with a default WWN. It will also create an ++initial target portal group called ++.IR tpgt1 . ++.P ++.B iqn.2003-01.org.linux-iscsi.test2.x8664:sn123456789012/tpgt1/ ++.br ++An example of changing to the configuration node for the given ++target's first target portal group (TPG). This is equivalent to giving ++the command prefixed by "cd". (Although more can be useful for certain ++setups, most configurations have a single TPG per target. In this ++case, configuring the TPG is equivalent to configuring the overall ++target.) ++.P ++.B portals/ create ++.br ++Add a portal, i.e. an address and TCP port via which the target can be ++contacted by initiators. Sane defaults are used if these are not ++specified. ++.P ++.B luns/ create /backstores/fileio0/disk1 ++.br ++Create a new LUN in the TPG, attached to the storage object that has ++previously been defined. The storage object now shows up under the ++/backstores configuration node as activated. ++.P ++.B acls/ create iqn.1994-05.com.redhat:4321576890 ++.br ++Creates an ACL (access control list) for the given iSCSI initiator. ++.P ++.B acls/iqn.1994-05.com.redhat:4321576890 create 2 0 ++.br ++Gives the initiator access to the first exported LUN (lun0), which the ++initiator will see as lun2. The default is to give the initiator ++read/write access; if read-only access was desired, an additional "1" ++argument would be added to enable write-protect. ++.. ++.SS OTHER COMMANDS ++.B saveconfig ++.br ++Save the current configuration settings to a file, from which ++settings will be restored if the system is rebooted. ++.P ++This command must be executed from the configuration root node. ++.P ++.B exit ++.br ++Leave the configuration shell. ++.SS SETTINGS ++.B set attribute authentication=0 ++.br ++Disable CHAP authentication. ++.P ++.B set global auto_cd_after_create=false ++.br ++Do not change into the configuration node of a newly created ++object. The default is 'true'. ++.SH FILES ++.B /etc/target/* ++.br ++.B /var/lib/target/* ++.SH AUTHOR ++Written by Jerome Martin . ++.br ++Man page written by Andy Grover . ++.SH REPORTING BUGS ++Report bugs to +-- +1.7.1 + diff --git a/0007-all-start.patch b/0007-all-start.patch new file mode 100644 index 0000000..477572a --- /dev/null +++ b/0007-all-start.patch @@ -0,0 +1,35 @@ +commit 1af9a2ce6f1ebb15a43439c7c438b59d69568fd5 +Author: Andy Grover +Date: Wed Nov 2 14:46:39 2011 -0700 + + append all start scripts into /etc/target/all_start.sh + + This should make systemd packaging a little easier. + + Signed-off-by: Andy Grover + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index 2048544..b3f78c4 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -89,6 +89,20 @@ class UIRoot(UINode): + else: + self.shell.log.warning("Aborted, configuration left untouched.") + ++ # append all into single script ++ from glob import iglob ++ import os ++ import shutil ++ sources = iglob(os.path.join("/etc/target", '*_start.sh')) ++ # ensure tcm_start is appended first ++ sources = [x for x in sources if not 'tcm_' in x] ++ sources = [x for x in sources if not 'all_' in x] ++ sources.insert(0, '/etc/target/tcm_start.sh') ++ dest = open('/etc/target/all_start.sh', 'wb') ++ for filename in sources: ++ shutil.copyfileobj(open(filename, 'rb'), dest) ++ dest.close() ++ + def ui_command_version(self): + ''' + Displays the targetcli and support libraries versions. diff --git a/sources b/sources index e69de29..049663c 100644 --- a/sources +++ b/sources @@ -0,0 +1 @@ +d90375b632ffb27700fa006b0e39e3f6 targetcli-1.99.2.gitb03ec79.tar.gz diff --git a/targetcli-git-version.patch b/targetcli-git-version.patch new file mode 100644 index 0000000..66ed45d --- /dev/null +++ b/targetcli-git-version.patch @@ -0,0 +1,13 @@ +diff --git a/rtsadmin/__init__.py b/rtsadmin/__init__.py +index 90150a8..c8a8f07 100644 +--- a/targetcli/__init__.py ++++ b/targetcli/__init__.py +@@ -17,7 +17,7 @@ along with this program. If not, see . + + from ui_root import UIRoot + +-__version__ = 'GIT_VERSION' ++__version__ = 'b03ec79' + __author__ = "Jerome Martin " + __url__ = "http://www.risingtidesystems.com" + __description__ = "An administration shell for RTS storage targets." diff --git a/targetcli.service b/targetcli.service new file mode 100644 index 0000000..1108bfc --- /dev/null +++ b/targetcli.service @@ -0,0 +1,13 @@ +[Unit] +Description=Loads LIO kernel target saved configuration +Requires=sys-kernel-config.mount + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=-/bin/sh /etc/target/all_start.sh +SyslogIdentifier=targetcli + +[Install] +WantedBy=multi-user.target + diff --git a/targetcli.spec b/targetcli.spec new file mode 100644 index 0000000..81e7b9a --- /dev/null +++ b/targetcli.spec @@ -0,0 +1,92 @@ +Name: targetcli +License: AGPLv3 +Group: System Environment/Libraries +Summary: An administration shell for storage targets +Version: 1.99.2.gitb03ec79 +Release: 3%{?dist} +# placeholder URL and source entries +# archive created using: +# git clone git://risingtidesystems.com/targetcli.git +# cd targetcli +# git archive b03ec79 --prefix targetcli-%{version}/ | gzip > targetcli-%{version}.tar.gz +URL: http://www.risingtidesystems.com/git/ +Source: %{name}-%{version}.tar.gz +Source1: targetcli.service +Patch1: targetcli-git-version.patch +Patch2: 0001-Remove-ads-from-cli-welcome-msg.-Mention-help-is-ava.patch +Patch3: 0002-bundle-lio-utils.patch +Patch4: 0003-Hack.-dump-scripts-aren-t-in-PATH-anymore-so-call-th.patch +Patch5: 0004-ignore-errors-from-failure-to-set-device-attributes.patch +Patch6: 0005-fix-spec_root-path.patch +Patch7: 0006-add-docs.patch +Patch8: 0007-all-start.patch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildArch: noarch +BuildRequires: python-devel python-rtslib python-configshell epydoc +BuildRequires: systemd-units +Requires: python-rtslib python-configshell +Requires(post): systemd-units + + +%description +An administration shell for configuring iSCSI, FCoE, and other +SCSI targets, using the TCM/LIO kernel target subsystem. FCoE +users will also need to install and use fcoe-utils. + + +%prep +%setup -q -n %{name}-%{version} +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 + +%build +%{__python} setup.py build +gzip --stdout targetcli.8 > targetcli.8.gz + +%install +rm -rf %{buildroot} +%{__python} setup.py install --skip-build --root %{buildroot} +mkdir -p %{buildroot}%{_sysconfdir}/rc.d/init.d +mkdir -p %{buildroot}%{_sysconfdir}/target/backup +mkdir -p %{buildroot}%{_mandir}/man8/ +mkdir -p %{buildroot}%{_unitdir} +install -m 644 %{SOURCE1} %{buildroot}%{_unitdir}/targetcli.service +install -m 644 targetcli.8.gz %{buildroot}%{_mandir}/man8/ + +%clean +rm -rf %{buildroot} + +%post +if [ $1 -eq 1 ] ; then + # Initial installation + /bin/systemctl enable targetcli.service >/dev/null 2>&1 || : +fi + +%files +%defattr(-,root,root,-) +%{python_sitelib} +%{_bindir}/targetcli +%{_unitdir}/targetcli.service +%dir %{_sysconfdir}/target/backup +%doc COPYING README +%{_mandir}/man8/targetcli.8.gz + +%changelog +* Wed Nov 2 2011 Andy Grover - 1.99.2.gitb03ec79-3 +- Add buildrequires for systemd-units +- use _unitdir +- remove preun, modify post + +* Wed Nov 2 2011 Andy Grover - 1.99.2.gitb03ec79-2 +- Add patch + * 0007-all-start.patch +- Replace sysv init with systemd init + +* Fri Oct 7 2011 Andy Grover - 1.99.2.gitb03ec79-1 +- Initial Fedora packaging