From 0fbc73a9c629fde4b25c971dc2965af24a32bfa3 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Fri, 5 Dec 2014 21:56:30 +0800 Subject: [PATCH] [Demo] Add access group support. * Only for demo, please don't commit. * TODO: * Split into small patches. * Review parameter variable naming scheme of new methods. * Add docstring to each method and detail explanation. Signed-off-by: Gris Ge --- targetd/block.py | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- targetd/utils.py | 8 ++ 2 files changed, 299 insertions(+), 14 deletions(-) diff --git a/targetd/block.py b/targetd/block.py index e8206b2..38a7108 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -16,8 +16,10 @@ # Routines to export block devices over iscsi. import contextlib -from rtslib import (Target, TPG, NodeACL, FabricModule, BlockStorageObject, RTSRoot, - NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS) +from rtslib import ( + Target, TPG, NodeACL, FabricModule, BlockStorageObject, RTSRoot, + NodeACLGroup, NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS, + MappedLUNGroup) import lvm from main import TargetdError from utils import ignored @@ -105,6 +107,15 @@ def initialize(config_dict): export_create=export_create, export_destroy=export_destroy, initiator_set_auth=initiator_set_auth, + access_group_list=access_group_list, + access_group_create=access_group_create, + access_group_init_add=access_group_init_add, + access_group_init_del=access_group_init_del, + access_group_destroy=access_group_destroy, + access_group_map_list=access_group_map_list, + access_group_map_create=access_group_map_create, + access_group_map_destroy=access_group_map_destroy, + access_group_set_auth=access_group_set_auth, ) @@ -180,6 +191,8 @@ def export_list(req): exports = [] for na in tpg.node_acls: + if na.tag: + continue for mlun in na.mapped_luns: mlun_vg, mlun_name = \ mlun.tpg_lun.storage_object.udev_path.split("/")[2:] @@ -192,16 +205,20 @@ def export_list(req): return exports -def export_create(req, pool, vol, initiator_wwn, lun): +def _tpg_lun_of(tpg, pool_name, vol_name): + """ + Return a object of LUN for given lvm lv. + If not exist, create one. + """ # get wwn of volume so LIO can export as vpd83 info - vg_name, thin_pool = get_vg_lv(pool) + vg_name, thin_pool = get_vg_lv(pool_name) with vgopen(vg_name) as vg: - vol_serial = vg.lvFromName(vol).getUuid() + vol_serial = vg.lvFromName(vol_name).getUuid() # only add new SO if it doesn't exist # so.name concats pool & vol names separated by ':' - so_name = "%s:%s" % (vg_name, vol) + so_name = "%s:%s" % (vg_name, vol_name) try: so = BlockStorageObject(so_name) except RTSLibError: @@ -212,6 +229,17 @@ def export_create(req, pool, vol, initiator_wwn, lun): with ignored(RTSLibError): so.set_attribute("emulate_model_alias", '1') + # only add tpg lun if it doesn't exist + for tmp_lun in tpg.luns: + if tmp_lun.storage_object.name == so.name and \ + tmp_lun.storage_object.plugin == 'block': + return tmp_lun + else: + return LUN(tpg, storage_object=so) + + +def export_create(req, pool, vol, initiator_wwn, lun): + fm = FabricModule('iscsi') t = Target(fm, target_name) tpg = TPG(t, 1) @@ -220,14 +248,14 @@ def export_create(req, pool, vol, initiator_wwn, lun): NetworkPortal(tpg, "0.0.0.0") na = NodeACL(tpg, initiator_wwn) - # only add tpg lun if it doesn't exist - for tmp_lun in tpg.luns: - if tmp_lun.storage_object.name == so.name \ - and tmp_lun.storage_object.plugin == 'block': - tpg_lun = tmp_lun - break - else: - tpg_lun = LUN(tpg, storage_object=so) + if na.tag: + # Dirty hack, we should not depend on internal design of NodeACLGroup + raise TargetdError( + TargetdError.ACTION_NOT_ALLOWED, + "Requested initiator_wwn is a member of access group, " + "please use 'access_group_map_create()' in stead") + + tpg_lun = _tpg_lun_of(tpg, pool, vol) # only add mapped lun if it doesn't exist for tmp_mlun in tpg_lun.mapped_luns: @@ -246,6 +274,13 @@ def export_destroy(req, pool, vol, initiator_wwn): tpg = TPG(t, 1) na = NodeACL(tpg, initiator_wwn) + if na.tag: + # Dirty hack, we should not depend on internal design of NodeACLGroup + raise TargetdError( + TargetdError.ACTION_NOT_ALLOWED, + "Requested initiator_wwn is a member of access group, " + "please use 'access_group_map_destroy()' in stead") + vg_name, thin_pool = get_vg_lv(pool) for mlun in na.mapped_luns: @@ -284,6 +319,13 @@ def initiator_set_auth(req, initiator_wwn, in_user, in_pass, out_user, tpg = TPG(t, 1) na = NodeACL(tpg, initiator_wwn) + if na.tag: + # Dirty hack, we should not depend on internal design of NodeACLGroup + raise TargetdError( + TargetdError.ACTION_NOT_ALLOWED, + "Requested initiator_wwn is a member of access group, " + "please use 'access_group_set_auth()' in stead") + if not in_user or not in_pass: # rtslib treats '' as its NULL value for these in_user = in_pass = '' @@ -326,3 +368,238 @@ def block_pools(req): type='block', uuid=thinp.getUuid())) return results + + +def _get_iscsi_tpg(): + fabric_module = FabricModule('iscsi') + target = Target(fabric_module, target_name) + return TPG(target, 1) + + +def access_group_list(req): + tpg = _get_iscsi_tpg() + results = [] + init_type = 'iscsi' + + for node_acl_group in tpg.node_acl_groups: + init_ids = list(node_acl_group.node_acls) + + results.append( + dict( + name=node_acl_group.name, + init_ids=init_ids, + init_type=init_type, + # For security concern, we don't expose password out. + # It can only changed. + chap_username=node_acl_group.chap_userid, + chap_username_mutual=node_acl_group.chap_mutual_userid, + ) + ) + return results + + +def access_group_create(req, ag_name, init_id, init_type): + if init_type != 'iscsi': + raise TargetdError( + TargetdError.NO_SUPPORT, "Only support iscsi") + + tpg = _get_iscsi_tpg() + + # Pre-check: + # 1. Name conflict: requested name is in use + # 2. Initiator conflict: request initiator is in other group + for node_acl_group in tpg.node_acl_groups: + if node_acl_group.name == ag_name: + raise TargetdError( + TargetdError.NAME_CONFLICT, + "Requested access group name is in use") + if init_id in list(node_acl_group.node_acls): + raise TargetdError( + TargetdError.EXISTS_INITIATOR, + "Requested init_id is used by other access group") + + node_acl_group = NodeACLGroup(tpg, ag_name) + node_acl_group.add_acl(init_id) + RTSRoot().save_to_file() + + +def access_group_init_add(req, ag_name, init_id, init_type): + if init_type != 'iscsi': + raise TargetdError( + TargetdError.NO_SUPPORT, "Only support iscsi") + + tpg = _get_iscsi_tpg() + # Pre-check: + # 1. Initiator not used by other access group. + # 2. ag_name exist. + found_node_acl_group = None + for node_acl_group in tpg.node_acl_groups: + if node_acl_group.name == ag_name: + found_node_acl_group = node_acl_group + if init_id in list(node_acl_group.node_acls): + # Already in requested group. + return + + if init_id in list(node_acl_group.node_acls): + raise TargetdError( + TargetdError.EXISTS_INITIATOR, + "Requested init_id is used by other access group") + + if found_node_acl_group: + found_node_acl_group.add_acl(init_id) + RTSRoot().save_to_file() + else: + raise TargetdError( + TargetdError.NOT_FOUND_ACCESS_GROUP, + "Requested access group name not found") + + +def _node_acl_group_of(tpg, ag_name): + node_acl_group = NodeACLGroup(tpg, ag_name) + + cur_init_ids = list(node_acl_group.node_acls) + + if len(cur_init_ids) == 0: + raise TargetdError( + TargetdError.NOT_FOUND_ACCESS_GROUP, + "Requested access group name not found") + return node_acl_group + + +def access_group_init_del(req, ag_name, init_id, init_type): + if init_type != 'iscsi': + raise TargetdError( + TargetdError.NO_SUPPORT, "Only support iscsi") + + # We can use NodeACL(mode='lookup') and then check NodeACL.tag for + # access group, but the intention of introduce NodeACLGroup is to + # hide that detail. + + tpg = _get_iscsi_tpg() + node_acl_group = _node_acl_group_of(tpg, ag_name) + + cur_init_ids = list(node_acl_group.node_acls) + + if init_id not in cur_init_ids: + return None + + if len(cur_init_ids) == 1: + # As empty access group does not keep volume maping and CHAP status + # we refused to maintain a empty access group. + raise TargetdError( + TargetdError.LAST_INIT_IN_ACCESS_GROUP, + "Refused to remove the last initiator from access group") + + node_acl_group.remove_acl(init_id) + RTSRoot().save_to_file() + + +def access_group_destroy(req, ag_name): + tpg = _get_iscsi_tpg() + node_acl_group = _node_acl_group_of(tpg, ag_name) + node_acl_group.delete() + RTSRoot().save_to_file() + +def access_group_map_list(req): + """ + Return a list of dictionaries in this format: + { + 'ag_name': ag_name, + 'h_lun_id': h_lun_id, # host side LUN ID + 't_lun_id': t_lun_id, # target side LUN ID + 'pool_name': pool_name, + 'vol_name': vol_name, + } + """ + results = [] + tpg = _get_iscsi_tpg() + vg_name_2_pool_name_dict = {} + for pool_name in pools: + vg_name = get_vg_lv(pool_name)[0] + vg_name_2_pool_name_dict[vg_name] = pool_name + + for node_acl_group in tpg.node_acl_groups: + for mapped_lun_group in node_acl_group.mapped_luns: + tpg_lun = mapped_lun_group.tpg_lun + so_name = tpg_lun.storage_object.name + (vg_name, vol_name) = so_name.split(":") + # Find out whether certain LVM LV is from thin pool. + results.append( + { + 'ag_name': node_acl_group.name, + 't_lun_id': tpg_lun.lun, + 'h_lun_id': mapped_lun_group.mapped_lun, + 'pool_name': vg_name_2_pool_name_dict[vg_name], + 'vol_name': vol_name, + } + ) + + return results + + +def access_group_map_create(req, pool_name, vol_name, ag_name, h_lun_id=None): + tpg = _get_iscsi_tpg() + tpg.enable = True + tpg.set_attribute("authentication", '0') + NetworkPortal(tpg, "0.0.0.0") + + tpg_lun = _tpg_lun_of(tpg, pool_name, vol_name) + + # Pre-Check: + # 1. Already mapped to requested access group, return None + if len(list(tpg_lun.mapped_luns)): + tgt_map_list = access_group_map_list(req) + for tgt_map in tgt_map_list: + if tgt_map['ag_name'] == ag_name and \ + tgt_map['pool_name'] == pool_name and \ + tgt_map['vol_name'] == vol_name: + # Already masked. + return None + + node_acl_group = _node_acl_group_of(tpg, ag_name) + + if h_lun_id is None: + # Find out next available host LUN ID + # Assuming max host LUN ID is LUN.MAX_LUN + free_h_lun_ids = set(range(LUN.MAX_LUN+1)) - \ + set([int(x.mapped_lun) for x in tpg_lun.mapped_luns]) + if len(free_h_lun_ids) == 0: + raise TargetdError( + TargetdError.NO_FREE_HOST_LUN_ID, + "All host LUN ID 0 ~ %d is in use" % LUN.MAX_LUN) + else: + h_lun_id = free_h_lun_ids.pop() + + node_acl_group.mapped_lun(h_lun_id, tpg_lun) + RTSRoot().save_to_file() + + +def access_group_map_destroy(req, pool_name, vol_name, ag_name): + tpg = _get_iscsi_tpg() + node_acl_group = _node_acl_group_of(tpg, ag_name) + tpg_lun = _tpg_lun_of(tpg, pool_name, vol_name) + for map_group in node_acl_group.mapped_luns: + if map_group.tpg_lun == tpg_lun: + map_group.delete() + + RTSRoot().save_to_file() + +def access_group_set_auth(req, ag_name, in_user, in_pass, out_user, + out_pass): + tpg = _get_iscsi_tpg() + node_acl_group = _node_acl_group_of(tpg, ag_name) + + if not in_user or not in_pass: + # rtslib treats '' as its NULL value for these + in_user = in_pass = '' + + if not out_user or not out_pass: + out_user = out_pass = '' + + node_acl_group.chap_userid = in_user + node_acl_group.chap_password = in_pass + + node_acl_group.chap_mutual_userid = out_user + node_acl_group.chap_mutual_password = out_pass + + RTSRoot().save_to_file() diff --git a/targetd/utils.py b/targetd/utils.py index 7150eba..256f5d1 100644 --- a/targetd/utils.py +++ b/targetd/utils.py @@ -27,6 +27,14 @@ def ignored(*exceptions): class TargetdError(Exception): + NAME_CONFLICT = -50 + EXISTS_INITIATOR = -52 + NO_SUPPORT = -153 + NOT_FOUND_ACCESS_GROUP = -200 + LAST_INIT_IN_ACCESS_GROUP = -502 + IS_MASKED = -160 + NO_FREE_HOST_LUN_ID = -1000 + ACTION_NOT_ALLOWED = -1001 def __init__(self, error_code, *args, **kwargs): Exception.__init__(self, *args, **kwargs) self.error = error_code -- 1.8.3.1