From 8da60e39b885a2d8cafbf4a74cf37f7766d47fb7 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Fri, 5 Dec 2014 21:59:16 +0800 Subject: [Demo] Add full access group support. * Just for demo, please don't commit. * No backward compatibility yet. * Tested for access group create/delete/edit, volume mask and unmask, volume mask status query. * TODO: * Split into small patches. * Add backward compatibility. * Handle TargetError.NO_FREE_HOST_LUN_ID as LsmError.SYSTEM_LIMIT. * Handle errors in volume mask/unmask. Signed-off-by: Gris Ge --- plugin/targetd/targetd.py | 276 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 210 insertions(+), 66 deletions(-) diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py index f5fde4e..ddf87e5 100644 --- a/plugin/targetd/targetd.py +++ b/plugin/targetd/targetd.py @@ -56,6 +56,10 @@ def target_wrapper(*args, **kwargs): class TargetdError(Exception): VOLUME_MASKED = 303 + NAME_CONFLICT = 50 + EXISTS_INITIATOR = 52 + NOT_FOUND_ACCESS_GROUP = 200 + LAST_INIT_IN_ACCESS_GROUP = 502 def __init__(self, errno, reason, *args, **kwargs): Exception.__init__(self, *args, **kwargs) @@ -212,115 +216,255 @@ def pools(self, search_key=None, search_value=None, flags=0): 'targetd')) return search_property(pools, search_key, search_value) + @staticmethod + def _tgt_ag_to_lsm(tgt_ag, system_id): + return AccessGroup( + tgt_ag['name'], tgt_ag['name'], + tgt_ag['init_ids'], AccessGroup.INIT_TYPE_ISCSI_IQN, + system_id) + @handle_errors def access_groups(self, search_key=None, search_value=None, flags=0): + rc = [] - for init_id in set(i['initiator_wwn'] - for i in self._jsonrequest("export_list")): - ag_id = md5(init_id) - init_type = AccessGroup.INIT_TYPE_ISCSI_IQN - ag_name = 'N/A' - init_ids = [init_id] - rc.extend( - [AccessGroup(ag_id, ag_name, init_ids, init_type, - self.system.id)]) + for tgt_ag in self._jsonrequest("access_group_list"): + rc.append(TargetdStorage._tgt_ag_to_lsm(tgt_ag, self.system.id)) + return search_property(rc, search_key, search_value) + def _search_lsm_ag_by_name(self, ag_name, lsm_error_obj=None): + """ + Raise provided error if defined when not found. + Return lsm.AccessGroup if found. + """ + lsm_ags = self.access_groups() + for lsm_ag in lsm_ags: + if lsm_ag.name == ag_name: + return lsm_ag + + if lsm_error_obj: + raise lsm_error_obj + + + @handle_errors + def access_group_create(self, name, init_id, init_type, system, flags=0): + if system.id != self.system.id: + raise LsmError( + ErrorNumber.NOT_FOUND_SYSTEM, + "System %s not found" % system.id) + try: + self._jsonrequest( + "access_group_create", + dict(ag_name=name, init_id=init_id, init_type='iscsi')) + except TargetdError as tgt_error: + if tgt_error.errno == TargetdError.EXISTS_INITIATOR: + raise LsmError( + ErrorNumber.EXISTS_INITIATOR, + "Initiator is already used by other access group") + elif tgt_error.errno == TargetdError.NAME_CONFLICT: + raise LsmError( + ErrorNumber.NAME_CONFLICT, + "Requested access group name is already used by other " + "access group") + else: + raise + + return self._search_lsm_ag_by_name( + name, + LsmError( + ErrorNumber.PLUGIN_BUG, + "access_group_create(): Failed to find the newly created access " + "group")) + + @handle_errors + def access_group_initiator_add(self, access_group, init_id, init_type, + flags=0): + + if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN: + raise LsmError( + ErrorNumber.NO_SUPPORT, + "Targetd only support iscsi") + + # Pre-check for NO_STATE_CHANGE error as targetd silently pass + # if initiator is already in requested access group. + flag_found = False + for lsm_ag in self.access_groups(): + if lsm_ag.id == access_group.id: + flag_found = True + if init_id in access_group.init_ids: + raise LsmError( + ErrorNumber.NO_STATE_CHANGE, + "Requested init_id is already in defined access " + "group") + + if flag_found is False: + raise LsmError( + ErrorNumber.NOT_FOUND_ACCESS_GROUP, + "Access group %s not found" % access_group.id) + + try: + self._jsonrequest( + "access_group_init_add", + dict( + ag_name=access_group.name, init_id=init_id, + init_type='iscsi')) + except TargetdError as tgt_error: + if tgt_error.errno == TargetdError.NOT_FOUND_ACCESS_GROUP: + # It might happen when someone else delete the access group + # right after we queryed. + raise LsmError( + ErrorNumber.NOT_FOUND_ACCESS_GROUP, + "Access group %s not found" % access_group.id) + elif tgt_error.errno == TargetdError.EXISTS_INITIATOR: + raise LsmError( + ErrorNumber.EXISTS_INITIATOR, + "Initiator is already used by other access group") + else: + raise + + return self._search_lsm_ag_by_name( + access_group.name, + LsmError( + ErrorNumber.PLUGIN_BUG, + "access_group_initiator_add(): " + "Failed to find the updated access group")) + + def access_group_initiator_delete(self, access_group, init_id, init_type, + flags=0): + if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN: + raise LsmError( + ErrorNumber.NO_SUPPORT, + "Targetd only support iscsi") + + # Pre-check for NO_STATE_CHANGE as targetd sliently return + # when init_id not in requested access_group. + lsm_ag = self._search_lsm_ag_by_name( + access_group.name, + LsmError( + ErrorNumber.NOT_FOUND_ACCESS_GROUP, + "Access group %s not found" % access_group.id)) + + if init_id not in lsm_ag.init_ids: + raise LsmError( + ErrorNumber.NO_STATE_CHANGE, + "Requested initiator is not in defined access group") + + try: + self._jsonrequest( + "access_group_init_del", + dict( + ag_name=access_group.name, + init_id=init_id, + init_type='iscsi')) + except TargetdError as tgt_error: + if tgt_error.errno == TargetdError.NOT_FOUND_ACCESS_GROUP: + raise LsmError( + ErrorNumber.NOT_FOUND_ACCESS_GROUP, + "Access group %s not found" % access_group.id) + elif tgt_error.errno == TargetdError.LAST_INIT_IN_ACCESS_GROUP: + raise LsmError( + ErrorNumber.LAST_INIT_IN_ACCESS_GROUP, + "Refused to remove the last initiator from access group") + else: + raise + + return self._search_lsm_ag_by_name( + access_group.name, + LsmError( + ErrorNumber.PLUGIN_BUG, + "access_group_initiator_delete(): " + "Failed to find the updated access group")) + + def access_group_delete(self, access_group, flags=0): + pass + + def _mask_infos(self): """ Return a list of tgt_mask: - 'vol_id': volume.id - 'ag_id': ag.id - 'lun_id': lun_id + '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, """ - tgt_masks = [] - tgt_exps = self._jsonrequest("export_list") - for tgt_exp in tgt_exps: - tgt_masks.extend([{ - 'vol_id': tgt_exp['vol_uuid'], - 'ag_id': md5(tgt_exp['initiator_wwn']), - 'lun_id': tgt_exp['lun'], - }]) - return tgt_masks - - def _is_masked(self, init_id, vol_id): + return self._jsonrequest("access_group_map_list") + + def _is_masked(self, ag_name, pool_name, vol_name): """ Check whether volume is masked to certain initiator. - Return Tuple (True,_mask_infos) or (False, _mask_infos) + Return True or False + Return Tuple (True, _mask_info) or (False, ) """ - ag_id = md5(init_id) - tgt_mask_infos = self._mask_infos() - for tgt_mask in tgt_mask_infos: - if tgt_mask['vol_id'] == vol_id and tgt_mask['ag_id'] == ag_id: - return True, tgt_mask_infos - return False, tgt_mask_infos + for tgt_mask in self._mask_infos(): + if tgt_mask['vol_name'] == vol_name and \ + tgt_mask['pool_name'] == pool_name and \ + tgt_mask['ag_name'] == ag_name: + return True + + return False def volume_mask(self, access_group, volume, flags=0): + if len(access_group.init_ids) == 0: raise LsmError(ErrorNumber.INVALID_ARGUMENT, "No member belong to defined access group: %s" % access_group.id) - if len(access_group.init_ids) != 1: - raise LsmError(ErrorNumber.NO_SUPPORT, - "Targetd does not allowing masking two or more " - "initiators to volume") if access_group.init_type != AccessGroup.INIT_TYPE_ISCSI_IQN: raise LsmError(ErrorNumber.NO_SUPPORT, "Targetd only support ISCSI initiator group type") - (is_masked, tgt_masks) = self._is_masked( - access_group.init_ids[0], volume.id) - - if is_masked: + if self._is_masked(access_group.name, volume.pool_id, volume.name): raise LsmError( ErrorNumber.NO_STATE_CHANGE, "Volume is already masked to requested access group") - # find lowest unused lun ID - used_lun_ids = [x['lun_id'] for x in tgt_masks] - lun_id = 0 - while True: - if lun_id in used_lun_ids: - lun_id += 1 - else: - break - - self._jsonrequest("export_create", - dict(pool=volume.pool_id, - vol=volume.name, - initiator_wwn=access_group.init_ids[0], - lun=lun_id)) + self._jsonrequest("access_group_map_create", + dict(pool_name=volume.pool_id, + vol_name=volume.name, + ag_name=access_group.name)) return None @handle_errors def volume_unmask(self, volume, access_group, flags=0): # Pre-check if already unmasked - if not self._is_masked(access_group.init_ids[0], volume.id)[0]: + if self._is_masked(access_group.name, volume.pool_id, volume.name): + self._jsonrequest("access_group_map_destroy", + dict(pool_name=volume.pool_id, + vol_name=volume.name, + ag_name=access_group.name)) + else: raise LsmError(ErrorNumber.NO_STATE_CHANGE, "Volume is not masked to requested access group") - else: - self._jsonrequest("export_destroy", - dict(pool=volume.pool_id, - vol=volume.name, - initiator_wwn=access_group.init_ids[0])) return None @handle_errors def volumes_accessible_by_access_group(self, access_group, flags=0): - tgt_masks = self._mask_infos() - vol_ids = list(x['vol_id'] for x in tgt_masks - if x['ag_id'] == access_group.id) + rc_lsm_vol_ids = [] lsm_vols = self.volumes(flags=flags) - return [x for x in lsm_vols if x.id in vol_ids] + for tgt_mask in self._mask_infos(): + if tgt_mask['ag_name'] == access_group.name: + rc_lsm_vol_ids.extend( + list( + v.id for v in lsm_vols + if v.pool_id == tgt_mask['pool_name'] and + v.name == tgt_mask['vol_name'])) + + return [v for v in lsm_vols if v.id in rc_lsm_vol_ids] @handle_errors def access_groups_granted_to_volume(self, volume, flags=0): - tgt_masks = self._mask_infos() - ag_ids = list(x['ag_id'] for x in tgt_masks - if x['vol_id'] == volume.id) + rc_lsm_ag_ids = [] lsm_ags = self.access_groups(flags=flags) - return [x for x in lsm_ags if x.id in ag_ids] + for tgt_mask in self._mask_infos(): + if tgt_mask['pool_name'] == volume.pool_id and \ + tgt_mask['vol_name'] == volume.name: + rc_lsm_ag_ids.extend( + list( + a.id for a in lsm_ags + if a.name == tgt_mask['ag_name'])) + return [a for a in lsm_ags if a.id in rc_lsm_ag_ids] def _get_volume(self, pool_id, volume_name): vol = [v for v in self._jsonrequest("vol_list", dict(pool=pool_id)) -- 1.8.3.1