Discussion:
[Libstoragemgmt-devel] [PATCH 0/6] Targetd Plugin: Introduce full access group support.
Gris Ge
2015-01-26 14:18:30 UTC
Permalink
* Requires:
* Targetd: 0.7.2 + '[PATCH 0/9] Introduce access group support' patch set.
* rtslib-fb: commit 27e9a1df9c004862fedea4786838a4e4acb8ef94
* Manually install rtslib-fb to /usr/lib/python2.7/site-packages/rtslib_fb
# There is a bug of 'make rpm' in rtslib-fb

* Tested via lsmcli including all possible LsmErrors of changed methods.

Gris Ge (6):
Target Plugin: Add new access group query support.
Targetd Plugin: Add access_group_create() support.
Targetd Plugin: Add access_group_initiator_add() support.
Targetd Plugin: Add access_group_initiator_delete() support.
Targetd Plugin: Add new targetd API support for volume mask.
Targetd Plugin: Add access_group_delete() support.

plugin/targetd/targetd.py | 434 ++++++++++++++++++++++++++++++++++++++--------
1 file changed, 363 insertions(+), 71 deletions(-)
--
1.8.3.1
Gris Ge
2015-01-26 14:18:31 UTC
Permalink
* Check whether true access group is supported or not in plugin_register().
If not, set self._flag_ag_support as False

* Use initiator_list() method of targetd for backward compatibility.
* Use access_group_list() method of targetd for new access group support.

This break the access group ID backward compatibility:

* Add 'init.' prefix to ID of fake access groups. With this we could
identify whether requested access group could be handled by
access_group_delete(), access_group_initiator_add() and etc methods.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 62 ++++++++++++++++++++++++++++++++++++++---------
1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index f5fde4e..0da0206 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -56,6 +56,7 @@ def handle_errors(method):

class TargetdError(Exception):
VOLUME_MASKED = 303
+ INVALID_METHOD = 32601

def __init__(self, errno, reason, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
@@ -64,6 +65,7 @@ class TargetdError(Exception):


class TargetdStorage(IStorageAreaNetwork, INfs):
+ _FAKE_AG_PREFIX = 'init.'

def __init__(self):
self.uri = None
@@ -82,6 +84,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
self.uri = uri_parse(uri)
self.password = password
self.tmo = timeout
+ self._flag_ag_support = True

user = self.uri.get('username', DEFAULT_USER)
port = self.uri.get('port', DEFAULT_PORT)
@@ -99,6 +102,14 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
self.headers = {'Content-Type': 'application/json',
'Authorization': 'Basic %s' % (auth,)}

+ try:
+ self._jsonrequest('access_group_list')
+ except TargetdError as te:
+ if te.errno == TargetdError.INVALID_METHOD:
+ self._flag_ag_support = False
+ else:
+ raise
+
@handle_errors
def time_out_set(self, ms, flags=0):
self.tmo = ms
@@ -212,19 +223,48 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
'targetd'))
return search_property(pools, search_key, search_value)

+
+ @staticmethod
+ def _tgt_ag_to_lsm(tgt_ag, sys_id):
+ return AccessGroup(
+ tgt_ag['name'], tgt_ag['name'], tgt_ag['init_ids'],
+ AccessGroup.INIT_TYPE_ISCSI_IQN, sys_id)
+
+ @staticmethod
+ def _tgt_init_to_lsm(tgt_init, sys_id):
+ return AccessGroup(
+ "%s%s" % (
+ TargetdStorage._FAKE_AG_PREFIX, md5(tgt_init['init_id'])),
+ 'N/A', [tgt_init['init_id']], AccessGroup.INIT_TYPE_ISCSI_IQN,
+ sys_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)])
- return search_property(rc, search_key, search_value)
+ rc_lsm_ags = []
+
+ # For backward compatibility
+ if self._flag_ag_support is True:
+ tgt_inits = self._jsonrequest(
+ 'initiator_list', {'standalone_only':True})
+ else:
+ tgt_inits = list(
+ {'init_id': x}
+ for x in set(
+ i['initiator_wwn']
+ for i in self._jsonrequest("export_list")))
+
+ rc_lsm_ags.extend(
+ list(
+ TargetdStorage._tgt_init_to_lsm(i, self.system.id)
+ for i in tgt_inits))
+
+ if self._flag_ag_support is True:
+ for tgt_ag in self._jsonrequest('access_group_list'):
+ rc_lsm_ags.append(
+ TargetdStorage._tgt_ag_to_lsm(
+ tgt_ag, self.system.id))
+
+ return search_property(rc_lsm_ags, search_key, search_value)

def _mask_infos(self):
"""
--
1.8.3.1
Gris Ge
2015-01-26 14:18:32 UTC
Permalink
* Introduced a private method: _search_lsm_ag_by_name() to search
access group with given name. It is required because targetd does not
return anything on access_group_create() call.

* Use access_group_create() method of targetd to create new access group.
Only iSCSI supported due to restriction of targetd.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 0da0206..febd5dc 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -57,6 +57,9 @@ def handle_errors(method):
class TargetdError(Exception):
VOLUME_MASKED = 303
INVALID_METHOD = 32601
+ INVALID_ARGUMENT = 32602
+ NAME_CONFLICT = -50
+ EXISTS_INITIATOR = 52

def __init__(self, errno, reason, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
@@ -266,6 +269,61 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

return search_property(rc_lsm_ags, 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
+
+ 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)
+ if self._flag_ag_support is False:
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "Please upgrade your targetd package to support "
+ "access_group_create()")
+
+ if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Only iSCSI yet")
+
+ 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")
+ elif tgt_error.errno == TargetdError.INVALID_ARGUMENT:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ str(tgt_error))
+ 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"))
+
def _mask_infos(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-01-26 14:18:33 UTC
Permalink
* Add access_group_initiator_add() and raise proper error as API document
required.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index febd5dc..efbe800 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -324,6 +324,46 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
"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")
+
+ lsm_ag = self._search_lsm_ag_by_name(
+ access_group.name,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
+
+ # Pre-check for NO_STATE_CHANGE error as targetd silently pass
+ # if initiator is already in requested access group.
+ if init_id in lsm_ag.init_ids:
+ raise LsmError(
+ ErrorNumber.NO_STATE_CHANGE,
+ "Requested init_id is already in defined access group")
+
+ 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.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 _mask_infos(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-01-26 14:18:36 UTC
Permalink
* Use 'access_group_destroy()' of targetd to do so.
* Raise NO_SUPPORT error if got fake access group.
* Raise NO_SUPPORT error if working against old targetd.
* Raise IS_MASKED error if has volume masked.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index e6f9e88..04ebf76 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -405,6 +405,34 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
"access_group_initiator_delete(): "
"Failed to find the updated access group"))

+ def access_group_delete(self, access_group, flags=0):
+ if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "Cannot delete old initiator simulated access group, "
+ "they will be automatically deleted when no volume masked to")
+
+ if self._flag_ag_support is False:
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "Please upgrade your targetd package to support "
+ "access_group_delete()")
+
+ self._lsm_ag_of_id(
+ access_group.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP,
+ "Access group not found"))
+
+ if list(m for m in self._tgt_masks() if m['ag_id'] == access_group.id):
+ raise LsmError(
+ ErrorNumber.IS_MASKED,
+ "Cannot delete access group which has volume masked to")
+
+ self._jsonrequest(
+ "access_group_destroy", {'ag_name': access_group.name})
+ return None
+
def _tgt_masks(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-01-26 14:18:34 UTC
Permalink
* Add access_group_initiator_delete() support and raise proper error
as API document required.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index efbe800..ac3b891 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -364,6 +364,44 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
"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 not found"))
+
+ if init_id not in lsm_ag.init_ids:
+ raise LsmError(
+ ErrorNumber.NO_STATE_CHANGE,
+ "Requested initiator is not in defined access group")
+
+ if len(lsm_ag.init_ids) == 1:
+ raise LsmError(
+ ErrorNumber.LAST_INIT_IN_ACCESS_GROUP,
+ "Refused to remove the last initiator from access group")
+
+ self._jsonrequest(
+ "access_group_init_del",
+ dict(
+ ag_name=access_group.name,
+ init_id=init_id,
+ init_type='iscsi'))
+
+ 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 _mask_infos(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-01-26 14:18:35 UTC
Permalink
* Changed _search_lsm_ag_by_name() to _lsm_ag_of_id() which now support
handling fake access group and true access group.
* Add _lsm_vol_of_id() for validating volume existence.

* Updated these public methods to support new targetd API:
* volume_mask()
* volume_unmask()
* access_groups_granted_to_volume()
* volumes_accessible_by_access_group()

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 218 ++++++++++++++++++++++++++++++++--------------
1 file changed, 153 insertions(+), 65 deletions(-)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index ac3b891..e6f9e88 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -60,6 +60,8 @@ class TargetdError(Exception):
INVALID_ARGUMENT = 32602
NAME_CONFLICT = -50
EXISTS_INITIATOR = 52
+ NO_FREE_HOST_LUN_ID = 1000
+ EMPTY_ACCESS_GROUP = 511

def __init__(self, errno, reason, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
@@ -69,6 +71,7 @@ class TargetdError(Exception):

class TargetdStorage(IStorageAreaNetwork, INfs):
_FAKE_AG_PREFIX = 'init.'
+ _MAX_H_LUN_ID = 255

def __init__(self):
self.uri = None
@@ -269,14 +272,14 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

return search_property(rc_lsm_ags, search_key, search_value)

- def _search_lsm_ag_by_name(self, ag_name, lsm_error_obj=None):
+ def _lsm_ag_of_id(self, ag_id, 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:
+ if lsm_ag.id == ag_id:
return lsm_ag

if lsm_error_obj:
@@ -317,7 +320,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
else:
raise

- return self._search_lsm_ag_by_name(
+ return self._lsm_ag_of_id(
name,
LsmError(
ErrorNumber.PLUGIN_BUG,
@@ -331,7 +334,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
raise LsmError(
ErrorNumber.NO_SUPPORT, "Targetd only support iscsi")

- lsm_ag = self._search_lsm_ag_by_name(
+ lsm_ag = self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
@@ -357,7 +360,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
else:
raise

- return self._search_lsm_ag_by_name(
+ return self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.PLUGIN_BUG,
@@ -373,7 +376,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

# 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(
+ lsm_ag = self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
@@ -395,106 +398,191 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
init_id=init_id,
init_type='iscsi'))

- return self._search_lsm_ag_by_name(
+ return self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.PLUGIN_BUG,
"access_group_initiator_delete(): "
"Failed to find the updated access group"))

- def _mask_infos(self):
+ def _tgt_masks(self):
"""
Return a list of tgt_mask:
- 'vol_id': volume.id
- 'ag_id': ag.id
- 'lun_id': lun_id
+ {
+ 'pool_name': pool_name,
+ 'vol_name': vol_name,
+ 'ag_id': lsm_ag.id,
+ 'h_lun_id': h_lun_id,
+ }
"""
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'],
- }])
+ for tgt_exp in self._jsonrequest("export_list"):
+ tgt_masks.append({
+ 'ag_id': "%s%s" % (
+ TargetdStorage._FAKE_AG_PREFIX,
+ md5(tgt_exp['initiator_wwn'])),
+ 'vol_name': tgt_exp['vol_name'],
+ 'pool_name': tgt_exp['pool'],
+ 'h_lun_id': tgt_exp['lun'],
+ })
+ for tgt_ag_map in self._jsonrequest("access_group_map_list"):
+ tgt_masks.append({
+ 'ag_id': tgt_ag_map['ag_name'],
+ 'vol_name': tgt_ag_map['vol_name'],
+ 'pool_name': tgt_ag_map['pool_name'],
+ 'h_lun_id': tgt_ag_map['h_lun_id'],
+ })
+
return tgt_masks

- def _is_masked(self, init_id, vol_id):
+ def _is_masked(self, ag_id, pool_name, vol_name, tgt_masks=None):
"""
- Check whether volume is masked to certain initiator.
- Return Tuple (True,_mask_infos) or (False, _mask_infos)
+ Check whether volume is masked to certain access group.
+ Return True 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
+ if tgt_masks is None:
+ tgt_masks = self._tgt_masks()
+ return list(
+ m for m in tgt_masks
+ if m['vol_name'] == vol_name and \
+ m['pool_name'] == pool_name and \
+ m['ag_id'] == ag_id) != []
+
+ def _lsm_vol_of_id(self, vol_id, error=None):
+ try:
+ return list(v for v in self.volumes() if v.id == vol_id)[0]
+ except IndexError:
+ if error:
+ raise error
+ else:
+ return None

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")
+ self._lsm_ag_of_id(
+ access_group.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))

- (is_masked, tgt_masks) = self._is_masked(
- access_group.init_ids[0], volume.id)
+ self._lsm_vol_of_id(
+ volume.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME, "Volume not found"))

- if is_masked:
+ tgt_masks = self._tgt_masks()
+ if self._is_masked(
+ access_group.id, volume.pool_id, volume.name, tgt_masks):
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
+ if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
+ free_h_lun_ids = (
+ set(range(TargetdStorage._MAX_H_LUN_ID + 1)) -
+ set([m['h_lun_id'] for m in tgt_masks]))
+
+ if len(free_h_lun_ids) == 0:
+ # TODO(Gris Ge): Add SYSTEM_LIMIT error into API
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "System limit: targetd only allows %s LUN masked" %
+ TargetdStorage._MAX_H_LUN_ID)
+
+ h_lun_id = free_h_lun_ids.pop()
+
+ self._jsonrequest(
+ "export_create",
+ {
+ 'pool': volume.pool_id,
+ 'vol': volume.name,
+ 'initiator_wwn': access_group.init_ids[0],
+ 'lun': h_lun_id
+ })
+ else:
+ try:
+ self._jsonrequest(
+ 'access_group_map_create',
+ {
+ 'pool_name': volume.pool_id,
+ 'vol_name': volume.name,
+ 'ag_name': access_group.id,
+ })
+ except TargetdError as tgt_error:
+ if tgt_error.errno == TargetdError.NO_FREE_HOST_LUN_ID:
+ # TODO(Gris Ge): Add SYSTEM_LIMIT error into API
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "System limit: targetd only allows %s LUN masked" %
+ TargetdStorage._MAX_H_LUN_ID)
+ elif tgt_error.errno == TargetdError.EMPTY_ACCESS_GROUP:
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP,
+ "Access group not found")
+ else:
+ raise

- self._jsonrequest("export_create",
- dict(pool=volume.pool_id,
- vol=volume.name,
- initiator_wwn=access_group.init_ids[0],
- lun=lun_id))
return None

@handle_errors
def volume_unmask(self, volume, access_group, flags=0):
+ self._lsm_ag_of_id(
+ access_group.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
+
+ self._lsm_vol_of_id(
+ volume.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME, "Volume not found"))
+
# Pre-check if already unmasked
- if not self._is_masked(access_group.init_ids[0], volume.id)[0]:
+ if not self._is_masked(access_group.id, volume.pool_id, volume.name):
raise LsmError(ErrorNumber.NO_STATE_CHANGE,
"Volume is not masked to requested access group")
- else:
+
+
+ if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
self._jsonrequest("export_destroy",
dict(pool=volume.pool_id,
vol=volume.name,
initiator_wwn=access_group.init_ids[0]))
+ else:
+ self._jsonrequest(
+ "access_group_map_destroy",
+ {
+ 'pool_name': volume.pool_id,
+ 'vol_name': volume.name,
+ 'ag_name': access_group.id,
+ })
+
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)
- lsm_vols = self.volumes(flags=flags)
- return [x for x in lsm_vols if x.id in vol_ids]
+ tgt_masks = self._tgt_masks()
+
+ vol_infos = list(
+ [m['vol_name'], m['pool_name']]
+ for m in tgt_masks
+ if m['ag_id'] == access_group.id)
+
+ if len(vol_infos) == 0:
+ return []
+
+ rc_lsm_vols = []
+ return list(
+ lsm_vol
+ for lsm_vol in self.volumes(flags=flags)
+ if [lsm_vol.name, lsm_vol.pool_id] in vol_infos)

@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)
+ tgt_masks = self._tgt_masks()
+ ag_ids = list(
+ m['ag_id']
+ for m in tgt_masks
+ if m['vol_name'] == volume.name and
+ m['pool_name'] == volume.pool_id)
+
lsm_ags = self.access_groups(flags=flags)
return [x for x in lsm_ags if x.id in ag_ids]
--
1.8.3.1
Tony Asleson
2015-01-27 00:27:22 UTC
Permalink
Hi Gris,
Post by Gris Ge
* Targetd: 0.7.2 + '[PATCH 0/9] Introduce access group support' patch set.
* rtslib-fb: commit 27e9a1df9c004862fedea4786838a4e4acb8ef94
* Manually install rtslib-fb to /usr/lib/python2.7/site-packages/rtslib_fb
# There is a bug of 'make rpm' in rtslib-fb
* Tested via lsmcli including all possible LsmErrors of changed methods.
Please try testing with the plugin_test.py and test the following to
ensure it works as expected (I haven't tried running the code yet):

* new plugin with old targetd service
* new targetd service with old plugin

Code review notes:

* Missing added capabilities, please add to capabilities method:
- ACCESS_GROUP_CREATE_ISCSI_IQN
- ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN
- ACCESS_GROUP_INITIATOR_DELETE
- ACCESS_GROUP_DELETE

* Add method decorator '@handle_errors' to methods:
- access_group_create
- access_group_initiator_delete
- access_group_delete
- volume_mask (missing before)

* Is there some reason TargetdError.NAME_CONFLICT is negative (-50) and
others are not? I haven't checked targetd source for error codes, just
noticed the difference.

* _is_masked used to return (bool, list), now returns list and used as
boolean. Fix comment or code or both.

* _tgt_masks calls json method 'access_group_map_list' which may not
exist in old service, will cause plug-in bug exception with new plugin,
old service.

* A few pep8 errors, but nothing major :-)

I would like Andy to take a peek at the plug-in patch in addition to
your targetd patches if at all possible too.

Thanks!

Regards,
Tony
Gris Ge
2015-01-27 15:07:50 UTC
Permalink
Post by Tony Asleson
Hi Gris,
Please try testing with the plugin_test.py and test the following to
* new plugin with old targetd service
* new targetd service with old plugin
Hi Tony,

Thank for the review. I will send V2 patch set after Andy reviewed the
targetd and(maybe) this patch set.

Best regards.
Post by Tony Asleson
Regards,
Tony
--
Gris Ge
Gris Ge
2015-02-03 10:37:20 UTC
Permalink
* Requires:
* Targetd:
master(31b3a1c5c99a708b5ea04f5049213dcb7537c503) +
[PATCH] Fix false alarm on "vol_destroy" about volume is masked.

* rtslib-fb: master(27e9a1df9c004862fedea4786838a4e4acb8ef94)

* Manually install rtslib-fb to /usr/lib/python2.7/site-packages/rtslib_fb
# There is a bug of 'make rpm' in rtslib-fb

Changes since V1:

* Add missing @handle_errors decorator.
* Fix incorrect negative constant value TargetdError.NAME_CONFLICT.
* Add missing capabilities.
* Fix _tgt_masks() method for PLUGIN_BUG issue on old plugin.
* Fix a bug in plugin test(patch 7/8)
* Fix PEP8 errors.

Tests:

* Test command is:
plugin_test.py TestPlugin.test_access_group_create_delete && \
plugin_test.py TestPlugin.test_access_group_list && \
plugin_test.py TestPlugin.test_access_group_initiator_add_delete && \
plugin_test.py TestPlugin.test_mask_unmask && echo "Test PASS"

* Tests passed on:
1. New targetd + old lsm code.
2. New targetd + new lsm code.
3. Old targetd + new lsm code.
4. Old targetd + old lsm code.

Gris Ge (8):
Target Plugin: Add new access group query support.
Targetd Plugin: Add access_group_create() support.
Targetd Plugin: Add access_group_initiator_add() support.
Targetd Plugin: Add access_group_initiator_delete() support.
Targetd Plugin: Add new targetd API support for volume mask.
Targetd Plugin: Add access_group_delete() support.
Plugin Test: Fix incorrect capability used in test_mask_unmask()
Targetd Plugin: PEP8 fix.

plugin/targetd/targetd.py | 445 ++++++++++++++++++++++++++++++++++++++--------
test/plugin_test.py | 2 +-
2 files changed, 375 insertions(+), 72 deletions(-)
--
1.8.3.1
Gris Ge
2015-02-03 10:37:21 UTC
Permalink
* Check whether true access group is supported or not in plugin_register().
If not, set self._flag_ag_support as False

* Use initiator_list() method of targetd for backward compatibility.
* Use access_group_list() method of targetd for new access group support.

This break the access group ID backward compatibility:

* Add 'init.' prefix to ID of fake access groups. With this we could
identify whether requested access group could be handled by
access_group_delete(), access_group_initiator_add() and etc methods.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 61 ++++++++++++++++++++++++++++++++++++++---------
1 file changed, 50 insertions(+), 11 deletions(-)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index f5fde4e..9bcaecd 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -56,6 +56,7 @@ def handle_errors(method):

class TargetdError(Exception):
VOLUME_MASKED = 303
+ INVALID_METHOD = 32601

def __init__(self, errno, reason, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
@@ -64,6 +65,7 @@ class TargetdError(Exception):


class TargetdStorage(IStorageAreaNetwork, INfs):
+ _FAKE_AG_PREFIX = 'init.'

def __init__(self):
self.uri = None
@@ -82,6 +84,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
self.uri = uri_parse(uri)
self.password = password
self.tmo = timeout
+ self._flag_ag_support = True

user = self.uri.get('username', DEFAULT_USER)
port = self.uri.get('port', DEFAULT_PORT)
@@ -99,6 +102,14 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
self.headers = {'Content-Type': 'application/json',
'Authorization': 'Basic %s' % (auth,)}

+ try:
+ self._jsonrequest('access_group_list')
+ except TargetdError as te:
+ if te.errno == TargetdError.INVALID_METHOD:
+ self._flag_ag_support = False
+ else:
+ raise
+
@handle_errors
def time_out_set(self, ms, flags=0):
self.tmo = ms
@@ -212,19 +223,47 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
'targetd'))
return search_property(pools, search_key, search_value)

+ @staticmethod
+ def _tgt_ag_to_lsm(tgt_ag, sys_id):
+ return AccessGroup(
+ tgt_ag['name'], tgt_ag['name'], tgt_ag['init_ids'],
+ AccessGroup.INIT_TYPE_ISCSI_IQN, sys_id)
+
+ @staticmethod
+ def _tgt_init_to_lsm(tgt_init, sys_id):
+ return AccessGroup(
+ "%s%s" % (
+ TargetdStorage._FAKE_AG_PREFIX, md5(tgt_init['init_id'])),
+ 'N/A', [tgt_init['init_id']], AccessGroup.INIT_TYPE_ISCSI_IQN,
+ sys_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)])
- return search_property(rc, search_key, search_value)
+ rc_lsm_ags = []
+
+ # For backward compatibility
+ if self._flag_ag_support is True:
+ tgt_inits = self._jsonrequest(
+ 'initiator_list', {'standalone_only': True})
+ else:
+ tgt_inits = list(
+ {'init_id': x}
+ for x in set(
+ i['initiator_wwn']
+ for i in self._jsonrequest("export_list")))
+
+ rc_lsm_ags.extend(
+ list(
+ TargetdStorage._tgt_init_to_lsm(i, self.system.id)
+ for i in tgt_inits))
+
+ if self._flag_ag_support is True:
+ for tgt_ag in self._jsonrequest('access_group_list'):
+ rc_lsm_ags.append(
+ TargetdStorage._tgt_ag_to_lsm(
+ tgt_ag, self.system.id))
+
+ return search_property(rc_lsm_ags, search_key, search_value)

def _mask_infos(self):
"""
--
1.8.3.1
Gris Ge
2015-02-03 10:37:23 UTC
Permalink
* Add access_group_initiator_add() and raise proper error as API document
required.

Changes in V2:

* Add missing capability:
Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 704bb41..1a81501 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -153,6 +153,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

if self._flag_ag_support:
cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)

return cap

@@ -327,6 +328,46 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
"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")
+
+ lsm_ag = self._search_lsm_ag_by_name(
+ access_group.name,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
+
+ # Pre-check for NO_STATE_CHANGE error as targetd silently pass
+ # if initiator is already in requested access group.
+ if init_id in lsm_ag.init_ids:
+ raise LsmError(
+ ErrorNumber.NO_STATE_CHANGE,
+ "Requested init_id is already in defined access group")
+
+ 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.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 _mask_infos(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-02-03 10:37:22 UTC
Permalink
* Introduced a private method: _search_lsm_ag_by_name() to search
access group with given name. It is required because targetd does not
return anything on access_group_create() call.

* Use access_group_create() method of targetd to create new access group.
Only iSCSI supported due to restriction of targetd.

Changes in V2:

* Fix incorrect negative constant TargetdError.NAME_CONFLICT.
In _jsonrequest(), the negative error number provided by targetd
will be converted to absolute value.

* Add missing capabilities:
Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN

* Add missing @handle_errors decorator to this public method:
* access_group_create()

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 9bcaecd..704bb41 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -57,6 +57,9 @@ def handle_errors(method):
class TargetdError(Exception):
VOLUME_MASKED = 303
INVALID_METHOD = 32601
+ INVALID_ARGUMENT = 32602
+ NAME_CONFLICT = 50
+ EXISTS_INITIATOR = 52

def __init__(self, errno, reason, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
@@ -148,6 +151,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
cap.set(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION)

+ if self._flag_ag_support:
+ cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
+
return cap

@handle_errors
@@ -265,6 +271,62 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

return search_property(rc_lsm_ags, 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)
+ if self._flag_ag_support is False:
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "Please upgrade your targetd package to support "
+ "access_group_create()")
+
+ if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Only iSCSI yet")
+
+ 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")
+ elif tgt_error.errno == TargetdError.INVALID_ARGUMENT:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ str(tgt_error))
+ 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"))
+
def _mask_infos(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-02-03 10:37:26 UTC
Permalink
* Use 'access_group_destroy()' of targetd to do so.
* Raise NO_SUPPORT error if got fake access group.
* Raise NO_SUPPORT error if working against old targetd.
* Raise IS_MASKED error if has volume masked.

Changes in V2:
* Add missing capability:
Capabilities.ACCESS_GROUP_DELETE

* Add missing @handle_errors decorator to this public method:
* access_group_delete()

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 9fdb01e..b1b00b3 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -158,6 +158,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
+ cap.set(Capabilities.ACCESS_GROUP_DELETE)

return cap

@@ -411,6 +412,35 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
"access_group_initiator_delete(): "
"Failed to find the updated access group"))

+ @handle_errors
+ def access_group_delete(self, access_group, flags=0):
+ if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "Cannot delete old initiator simulated access group, "
+ "they will be automatically deleted when no volume masked to")
+
+ if self._flag_ag_support is False:
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "Please upgrade your targetd package to support "
+ "access_group_delete()")
+
+ self._lsm_ag_of_id(
+ access_group.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP,
+ "Access group not found"))
+
+ if list(m for m in self._tgt_masks() if m['ag_id'] == access_group.id):
+ raise LsmError(
+ ErrorNumber.IS_MASKED,
+ "Cannot delete access group which has volume masked to")
+
+ self._jsonrequest(
+ "access_group_destroy", {'ag_name': access_group.name})
+ return None
+
def _tgt_masks(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Gris Ge
2015-02-03 10:37:25 UTC
Permalink
* Changed _search_lsm_ag_by_name() to _lsm_ag_of_id() which now support
handling fake access group and true access group.
* Add _lsm_vol_of_id() for validating volume existence.

* Updated these public methods to support new targetd API:
* volume_mask()
* volume_unmask()
* access_groups_granted_to_volume()
* volumes_accessible_by_access_group()

Changes in V2:

* Check access group support status before call out 'access_group_map_list'
in _tgt_masks() method.

* Add missing @handle_errors decorator to these public methods:
* volume_mask()

* PEP8 fix.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 220 ++++++++++++++++++++++++++++++++--------------
1 file changed, 155 insertions(+), 65 deletions(-)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 9b0059d..9fdb01e 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -60,6 +60,8 @@ class TargetdError(Exception):
INVALID_ARGUMENT = 32602
NAME_CONFLICT = 50
EXISTS_INITIATOR = 52
+ NO_FREE_HOST_LUN_ID = 1000
+ EMPTY_ACCESS_GROUP = 511

def __init__(self, errno, reason, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
@@ -69,6 +71,7 @@ class TargetdError(Exception):

class TargetdStorage(IStorageAreaNetwork, INfs):
_FAKE_AG_PREFIX = 'init.'
+ _MAX_H_LUN_ID = 255

def __init__(self):
self.uri = None
@@ -273,14 +276,14 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

return search_property(rc_lsm_ags, search_key, search_value)

- def _search_lsm_ag_by_name(self, ag_name, lsm_error_obj=None):
+ def _lsm_ag_of_id(self, ag_id, 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:
+ if lsm_ag.id == ag_id:
return lsm_ag

if lsm_error_obj:
@@ -322,7 +325,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
else:
raise

- return self._search_lsm_ag_by_name(
+ return self._lsm_ag_of_id(
name,
LsmError(
ErrorNumber.PLUGIN_BUG,
@@ -336,7 +339,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
raise LsmError(
ErrorNumber.NO_SUPPORT, "Targetd only support iscsi")

- lsm_ag = self._search_lsm_ag_by_name(
+ lsm_ag = self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
@@ -362,7 +365,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
else:
raise

- return self._search_lsm_ag_by_name(
+ return self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.PLUGIN_BUG,
@@ -379,7 +382,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):

# 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(
+ lsm_ag = self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
@@ -401,106 +404,193 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
init_id=init_id,
init_type='iscsi'))

- return self._search_lsm_ag_by_name(
+ return self._lsm_ag_of_id(
access_group.name,
LsmError(
ErrorNumber.PLUGIN_BUG,
"access_group_initiator_delete(): "
"Failed to find the updated access group"))

- def _mask_infos(self):
+ def _tgt_masks(self):
"""
Return a list of tgt_mask:
- 'vol_id': volume.id
- 'ag_id': ag.id
- 'lun_id': lun_id
+ {
+ 'pool_name': pool_name,
+ 'vol_name': vol_name,
+ 'ag_id': lsm_ag.id,
+ 'h_lun_id': h_lun_id,
+ }
"""
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'],
- }])
+ for tgt_exp in self._jsonrequest("export_list"):
+ tgt_masks.append({
+ 'ag_id': "%s%s" % (
+ TargetdStorage._FAKE_AG_PREFIX,
+ md5(tgt_exp['initiator_wwn'])),
+ 'vol_name': tgt_exp['vol_name'],
+ 'pool_name': tgt_exp['pool'],
+ 'h_lun_id': tgt_exp['lun'],
+ })
+ if self._flag_ag_support:
+ for tgt_ag_map in self._jsonrequest("access_group_map_list"):
+ tgt_masks.append({
+ 'ag_id': tgt_ag_map['ag_name'],
+ 'vol_name': tgt_ag_map['vol_name'],
+ 'pool_name': tgt_ag_map['pool_name'],
+ 'h_lun_id': tgt_ag_map['h_lun_id'],
+ })
+
return tgt_masks

- def _is_masked(self, init_id, vol_id):
+ def _is_masked(self, ag_id, pool_name, vol_name, tgt_masks=None):
"""
- Check whether volume is masked to certain initiator.
- Return Tuple (True,_mask_infos) or (False, _mask_infos)
+ Check whether volume is masked to certain access group.
+ Return True 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
+ if tgt_masks is None:
+ tgt_masks = self._tgt_masks()
+ return list(
+ m for m in tgt_masks
+ if (m['vol_name'] == vol_name and
+ m['pool_name'] == pool_name and
+ m['ag_id'] == ag_id)
+ ) != []
+
+ def _lsm_vol_of_id(self, vol_id, error=None):
+ try:
+ return list(v for v in self.volumes() if v.id == vol_id)[0]
+ except IndexError:
+ if error:
+ raise error
+ else:
+ return None

+ @handle_errors
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")
+ self._lsm_ag_of_id(
+ access_group.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))

- (is_masked, tgt_masks) = self._is_masked(
- access_group.init_ids[0], volume.id)
+ self._lsm_vol_of_id(
+ volume.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME, "Volume not found"))

- if is_masked:
+ tgt_masks = self._tgt_masks()
+ if self._is_masked(
+ access_group.id, volume.pool_id, volume.name, tgt_masks):
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
+ if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
+ free_h_lun_ids = (
+ set(range(TargetdStorage._MAX_H_LUN_ID + 1)) -
+ set([m['h_lun_id'] for m in tgt_masks]))
+
+ if len(free_h_lun_ids) == 0:
+ # TODO(Gris Ge): Add SYSTEM_LIMIT error into API
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "System limit: targetd only allows %s LUN masked" %
+ TargetdStorage._MAX_H_LUN_ID)
+
+ h_lun_id = free_h_lun_ids.pop()
+
+ self._jsonrequest(
+ "export_create",
+ {
+ 'pool': volume.pool_id,
+ 'vol': volume.name,
+ 'initiator_wwn': access_group.init_ids[0],
+ 'lun': h_lun_id
+ })
+ else:
+ try:
+ self._jsonrequest(
+ 'access_group_map_create',
+ {
+ 'pool_name': volume.pool_id,
+ 'vol_name': volume.name,
+ 'ag_name': access_group.id,
+ })
+ except TargetdError as tgt_error:
+ if tgt_error.errno == TargetdError.NO_FREE_HOST_LUN_ID:
+ # TODO(Gris Ge): Add SYSTEM_LIMIT error into API
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "System limit: targetd only allows %s LUN masked" %
+ TargetdStorage._MAX_H_LUN_ID)
+ elif tgt_error.errno == TargetdError.EMPTY_ACCESS_GROUP:
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP,
+ "Access group not found")
+ else:
+ raise

- self._jsonrequest("export_create",
- dict(pool=volume.pool_id,
- vol=volume.name,
- initiator_wwn=access_group.init_ids[0],
- lun=lun_id))
return None

@handle_errors
def volume_unmask(self, volume, access_group, flags=0):
+ self._lsm_ag_of_id(
+ access_group.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
+
+ self._lsm_vol_of_id(
+ volume.id,
+ LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME, "Volume not found"))
+
# Pre-check if already unmasked
- if not self._is_masked(access_group.init_ids[0], volume.id)[0]:
+ if not self._is_masked(access_group.id, volume.pool_id, volume.name):
raise LsmError(ErrorNumber.NO_STATE_CHANGE,
"Volume is not masked to requested access group")
- else:
+
+ if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
self._jsonrequest("export_destroy",
dict(pool=volume.pool_id,
vol=volume.name,
initiator_wwn=access_group.init_ids[0]))
+ else:
+ self._jsonrequest(
+ "access_group_map_destroy",
+ {
+ 'pool_name': volume.pool_id,
+ 'vol_name': volume.name,
+ 'ag_name': access_group.id,
+ })
+
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)
- lsm_vols = self.volumes(flags=flags)
- return [x for x in lsm_vols if x.id in vol_ids]
+ tgt_masks = self._tgt_masks()
+
+ vol_infos = list(
+ [m['vol_name'], m['pool_name']]
+ for m in tgt_masks
+ if m['ag_id'] == access_group.id)
+
+ if len(vol_infos) == 0:
+ return []
+
+ rc_lsm_vols = []
+ return list(
+ lsm_vol
+ for lsm_vol in self.volumes(flags=flags)
+ if [lsm_vol.name, lsm_vol.pool_id] in vol_infos)

@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)
+ tgt_masks = self._tgt_masks()
+ ag_ids = list(
+ m['ag_id']
+ for m in tgt_masks
+ if (m['vol_name'] == volume.name and
+ m['pool_name'] == volume.pool_id))
+
lsm_ags = self.access_groups(flags=flags)
return [x for x in lsm_ags if x.id in ag_ids]
--
1.8.3.1
Gris Ge
2015-02-03 10:37:27 UTC
Permalink
* test_mask_unmask() is incorrectly expecting ACCESS_GROUP_CREATE_WWPN
capability to create a actual iSCSI access group.
This patch fix it by change it to correct ACCESS_GROUP_CREATE_ISCSI_IQN.

Signed-off-by: Gris Ge <***@redhat.com>
---
test/plugin_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/plugin_test.py b/test/plugin_test.py
index 8e0d8bc..33f28ce 100755
--- a/test/plugin_test.py
+++ b/test/plugin_test.py
@@ -696,7 +696,7 @@ class TestPlugin(unittest.TestCase):
Cap.VOLUME_CREATE,
Cap.VOLUME_DELETE]):

- if supported(cap, [Cap.ACCESS_GROUP_CREATE_WWPN]):
+ if supported(cap, [Cap.ACCESS_GROUP_CREATE_ISCSI_IQN]):
ag_name = rs("ag")
ag_iqn = 'iqn.1994-05.com.domain:01.' + rs(None, 6)
--
1.8.3.1
Gris Ge
2015-02-03 10:37:28 UTC
Permalink
* Just fix this two blank links issue generated by python-PEP8:
targetd.py:41:1: E302 expected 2 blank lines, found 1

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 1 +
1 file changed, 1 insertion(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index b1b00b3..a5e0274 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -38,6 +38,7 @@ PATH = "/targetrpc"
# Current sector size in liblvm
_LVM_SECTOR_SIZE = 512

+
def handle_errors(method):
def target_wrapper(*args, **kwargs):
try:
--
1.8.3.1
Gris Ge
2015-02-03 10:37:24 UTC
Permalink
* Add access_group_initiator_delete() support and raise proper error
as API document required.

Changes in V2:
* Add missing capabilities:
Capabilities.ACCESS_GROUP_INITIATOR_DELETE

* Add missing @handle_errors decorator to this public method:
* access_group_initiator_delete()

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/targetd/targetd.py | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 1a81501..9b0059d 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -154,6 +154,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
if self._flag_ag_support:
cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)

return cap

@@ -368,6 +369,45 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
"access_group_initiator_add(): "
"Failed to find the updated access group"))

+ @handle_errors
+ 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 not found"))
+
+ if init_id not in lsm_ag.init_ids:
+ raise LsmError(
+ ErrorNumber.NO_STATE_CHANGE,
+ "Requested initiator is not in defined access group")
+
+ if len(lsm_ag.init_ids) == 1:
+ raise LsmError(
+ ErrorNumber.LAST_INIT_IN_ACCESS_GROUP,
+ "Refused to remove the last initiator from access group")
+
+ self._jsonrequest(
+ "access_group_init_del",
+ dict(
+ ag_name=access_group.name,
+ init_id=init_id,
+ init_type='iscsi'))
+
+ 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 _mask_infos(self):
"""
Return a list of tgt_mask:
--
1.8.3.1
Tony Asleson
2015-02-03 17:11:11 UTC
Permalink
Hi Gris,

Patch set looks good, committed!

Thanks,
Tony
Post by Gris Ge
master(31b3a1c5c99a708b5ea04f5049213dcb7537c503) +
[PATCH] Fix false alarm on "vol_destroy" about volume is masked.
* rtslib-fb: master(27e9a1df9c004862fedea4786838a4e4acb8ef94)
* Manually install rtslib-fb to /usr/lib/python2.7/site-packages/rtslib_fb
# There is a bug of 'make rpm' in rtslib-fb
* Fix incorrect negative constant value TargetdError.NAME_CONFLICT.
* Add missing capabilities.
* Fix _tgt_masks() method for PLUGIN_BUG issue on old plugin.
* Fix a bug in plugin test(patch 7/8)
* Fix PEP8 errors.
plugin_test.py TestPlugin.test_access_group_create_delete && \
plugin_test.py TestPlugin.test_access_group_list && \
plugin_test.py TestPlugin.test_access_group_initiator_add_delete && \
plugin_test.py TestPlugin.test_mask_unmask && echo "Test PASS"
1. New targetd + old lsm code.
2. New targetd + new lsm code.
3. Old targetd + new lsm code.
4. Old targetd + old lsm code.
Target Plugin: Add new access group query support.
Targetd Plugin: Add access_group_create() support.
Targetd Plugin: Add access_group_initiator_add() support.
Targetd Plugin: Add access_group_initiator_delete() support.
Targetd Plugin: Add new targetd API support for volume mask.
Targetd Plugin: Add access_group_delete() support.
Plugin Test: Fix incorrect capability used in test_mask_unmask()
Targetd Plugin: PEP8 fix.
plugin/targetd/targetd.py | 445 ++++++++++++++++++++++++++++++++++++++--------
test/plugin_test.py | 2 +-
2 files changed, 375 insertions(+), 72 deletions(-)
Loading...