* To test this patch set: 1. Install rtslib-fb at commit 27e9a1df9c004862fedea4786838a4e4acb8ef94 cp <git_folder>/rtslib /usr/lib/python2.7/site-packages/rtslib_fb 2. Apply this patch set. 3. Restart targetd. 4. Apply this patch set to libstoragemgmt developer tree[1]: [PATCH 0/6] Targetd Plugin: Introduce full access group support 5. Test these commands: lsmcli ac --name gris_ag_03 --init iqn.2000-04.com.redhat:gris.03 \ --sys targetd lsmcli aa --ag gris_ag_03 --init iqn.2000-04.com.redhat:gris.13 lsmcli ar --ag gris_ag_03 --init iqn.2000-04.com.redhat:gris.13 lsmcli vm --vol gQTwyb-PoV5-NxWw-gMn1-0u7X-2tGp-8iaV0A --ag gris_ag_03 lsmcli vu --vol gQTwyb-PoV5-NxWw-gMn1-0u7X-2tGp-8iaV0A --ag gris_ag_03 lsmcli ad --ag gris_ag_03
* Tested against libstoragemgmt targetd plugin patch. * Let me know if your prefer I submit this via github pull request.
[1] b933bccad3ee29538f6200ae1b26e3644e9358fe
Gris Ge (9): New method: initiator_list(standalone_only=False) New method: access_group_list() New method: access_group_create() New method: access_group_destroy(ag_name) New method: access_group_init_add(ag_name, init_id, init_type) New method: access_group_init_del(ag_name, init_id, init_type) New method: access_group_map_list() New method: access_group_map_create(pool_name, vol_name, ag_name, h_lun_id=None) New method: access_group_map_destroy(pool_name, vol_name, ag_name)
API.md | 148 +++++++++++++++++++++++++- targetd/block.py | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++----- targetd/main.py | 6 +- targetd/utils.py | 18 ++++ 4 files changed, 455 insertions(+), 32 deletions(-)
The 'standalone_only' parameter is optional. Default is False which means all initiators will be included in results. When standalone_only is True, only return initiator which is not in any NodeACLGroup.
Return a list of initiator in the format of
[ { 'init_id': str, 'init_type': str, }, ]
The 'init_id' of result is the iSCSI IQN/NAA/EUI address of initiator. Example: 'iqn.2000-04.com.example:someone-01' The 'init_type' is reserved for future use, currently, it is 'iscsi'.
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 23 ++++++++++++++++++++++- targetd/block.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/API.md b/API.md index c33625e..9db742a 100644 --- a/API.md +++ b/API.md @@ -101,6 +101,27 @@ direction. Calling this method is not required for exports to work. If it is not called, exports require no authentication.
+### initiator_list(standalone_only=False) +List all initiators. +Parameters: + standalone_only(bool, optional): + If 'standalone_only' is True, only return initiator which is not in any + NodeACLGroup. + By default, all initiators will be included in result. +Returns: + [ + { + 'init_id': str, + 'init_type': str, + }, + ] + The 'init_id' of result is the iSCSI IQN/NAA/EUI address of initiator. + Example: 'iqn.2000-04.com.example:someone-01' + The 'init_type' is reserved for future use, currently, it is 'iscsi'. + +Errors: + N/A + File system operations ---------------------- Ability to create different file systems and perform operation on them. The @@ -147,7 +168,7 @@ Returns an array of export objects. Each export object contains: `host`, `path`
### nfs_export_add(host, path, options) Adds a NFS export given a `host`, and an export `path` to export and a list of `options` -Options is a list of NFS export options. Each option can be either a single value +Options is a list of NFS export options. Each option can be either a single value eg. no_root_squash or can be a `key=value` like `sec=sys`. See `man 5 exports` for all available supported options and the formats supported for `host`.
diff --git a/targetd/block.py b/targetd/block.py index e8206b2..b17c58a 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -105,6 +105,7 @@ def initialize(config_dict): export_create=export_create, export_destroy=export_destroy, initiator_set_auth=initiator_set_auth, + initiator_list=initiator_list, )
@@ -326,3 +327,43 @@ 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 initiator_list(req, standalone_only=False): + """Return a list of initiator + + Iterate all iSCSI rtslib-fb.NodeACL via rtslib-fb.TPG.node_acls(). + Args: + req (TargetHandler): Reserved for future use. + standalone_only (bool): + When standalone_only is True, only return initiator which is not + in any NodeACLGroup (NodeACL.tag is None). + Returns: + [ + { + 'init_id': NodeACL.node_wwn, + 'init_type': 'iscsi', + }, + ] + + Currently, targetd only support iscsi which means 'init_type' is + always 'iscsi'. + Raises: + N/A + """ + def _condition(node_acl, standalone_only): + if standalone_only and node_acl.tag is not None: + return False + else: + return True + + return list( + {'init_id': node_acl.node_wwn, 'init_type': 'iscsi'} + for node_acl in _get_iscsi_tpg().node_acls + if _condition(node_acl, standalone_only))
* Use rtslib_fb instead of 'rtslib' as upstream changed. * New method access_group_list(): Return a list of access group in the format of
[ { 'name': str 'init_ids': list(str), 'init_type': str, }, ]
The 'name' is the name of acccess group. The 'init_ids' of result is the iSCSI IQN/NAA/EUI address of initiators which belong to current access group. Example: ['iqn.2000-04.com.example:someone-01'] The 'init_type' is reserved for future use, currently, it is 'iscsi'
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 27 +++++++++++++++++++++++++++ targetd/block.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-)
diff --git a/API.md b/API.md index 9db742a..56044ad 100644 --- a/API.md +++ b/API.md @@ -122,6 +122,33 @@ Returns: Errors: N/A
+Access Group operations +----------------------- +Access Group is a group of initiators which sharing the same volume mapping +status. + +### access_group_list() +List all access groups. + +Parameters: + N/A +Returns: + [ + { + 'name': str + 'init_ids': list(str), + 'init_type': str, + }, + ] + The 'name' is the name of acccess group. + The 'init_ids' of result is the iSCSI IQN/NAA/EUI address of initiators + which belong to current access group. + Example: ['iqn.2000-04.com.example:someone-01'] + The 'init_type' is reserved for future use, currently, it is 'iscsi'. + +Errors: + N/A + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index b17c58a..2e185ef 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -16,8 +16,9 @@ # 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_fb import ( + Target, TPG, NodeACL, FabricModule, BlockStorageObject, RTSRoot, + NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS) import lvm from main import TargetdError from utils import ignored @@ -106,6 +107,7 @@ def initialize(config_dict): export_destroy=export_destroy, initiator_set_auth=initiator_set_auth, initiator_list=initiator_list, + access_group_list=access_group_list, )
@@ -367,3 +369,31 @@ def initiator_list(req, standalone_only=False): {'init_id': node_acl.node_wwn, 'init_type': 'iscsi'} for node_acl in _get_iscsi_tpg().node_acls if _condition(node_acl, standalone_only)) + + +def access_group_list(req): + """Return a list of access group + + Iterate all iSCSI rtslib-fb.NodeACLGroup via rtslib-fb.TPG.node_acls(). + Args: + req (TargetHandler): Reserved for future use. + Returns: + [ + { + 'name': str, + 'init_ids': list(str), + 'init_type': 'iscsi', + }, + ] + Currently, targetd only support iscsi which means init_type is always + 'iscsi'. + Raises: + N/A + """ + return list( + { + 'name': node_acl_group.name, + 'init_ids': list(node_acl_group.wwns), + 'init_type': 'iscsi', + } + for node_acl_group in _get_iscsi_tpg().node_acl_groups)
* New method name_check() in utils.py to enforce name API document definition in 'Conventions' section: '[a-z][A-Z][0-9]_-'
* New constants: TargetdError.INVALID_ARGUMENT TargetdError.NO_SUPPORT TargetdError.NAME_CONFLICT TargetdError.EXISTS_INITIATOR
* New public method: access_group_create(ag_name, init_id, init_type): Create new access group. Parameters: ag_name (str): Access group name init_id (str): iSCSI initiator address init_type (str): Reserved for future use. Should be set as 'iscsi' Returns: N/A Errors: -32602: Invalid parameter. Provided ag_name is illegal. Check 'Conventions' for detail. -153: No support. The 'init_type' is not 'iscsi' -50: Name conflict. Requested 'ag_name' is in use -52: Exists initiator. Requested 'init_id' is in use
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 16 ++++++++++++++++ targetd/block.py | 34 ++++++++++++++++++++++++++++++++-- targetd/main.py | 6 ++++-- targetd/utils.py | 16 ++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-)
diff --git a/API.md b/API.md index 56044ad..cb737b3 100644 --- a/API.md +++ b/API.md @@ -149,6 +149,22 @@ Returns: Errors: N/A
+### access_group_create(ag_name, init_id, init_type) +Create new access group. + +Parameters: + ag_name (str): Access group name + init_id (str): iSCSI initiator address + init_type (str): Reserved for future use. Should be set as 'iscsi' +Returns: + N/A +Errors: + -32602: Invalid parameter. Provided 'ag_name' is illegal. Check + 'Conventions' for detail. + -153: No support. The 'init_type' is not 'iscsi' + -50: Name conflict. Requested 'ag_name' is in use + -52: Exists initiator. Requested 'init_id' is in use + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index 2e185ef..71c843b 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -18,10 +18,10 @@ import contextlib from rtslib_fb import ( Target, TPG, NodeACL, FabricModule, BlockStorageObject, RTSRoot, - NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS) + NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS, NodeACLGroup) import lvm from main import TargetdError -from utils import ignored +from utils import ignored, name_check
def get_vg_lv(pool_name): @@ -108,6 +108,7 @@ def initialize(config_dict): initiator_set_auth=initiator_set_auth, initiator_list=initiator_list, access_group_list=access_group_list, + access_group_create=access_group_create, )
@@ -397,3 +398,32 @@ def access_group_list(req): 'init_type': 'iscsi', } for node_acl_group in _get_iscsi_tpg().node_acl_groups) + + +def access_group_create(req, ag_name, init_id, init_type): + if init_type != 'iscsi': + raise TargetdError( + TargetdError.NO_SUPPORT, "Only support iscsi") + + name_check(ag_name) + + tpg = _get_iscsi_tpg() + + # Pre-check: + # 1. Name conflict: requested name is in use + # 2. Initiator conflict: request initiator is in use + + 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(i.node_wwn for i in tpg.node_acls): + raise TargetdError( + TargetdError.EXISTS_INITIATOR, + "Requested init_id is in use") + + node_acl_group = NodeACLGroup(tpg, ag_name) + node_acl_group.add_acl(init_id) + RTSRoot().save_to_file() diff --git a/targetd/main.py b/targetd/main.py index 8af5300..04aff5e 100644 --- a/targetd/main.py +++ b/targetd/main.py @@ -113,7 +113,9 @@ class TargetHandler(BaseHTTPRequestHandler): log.debug(traceback.format_exc()) raise except TypeError: - error = (-32602, "invalid method parameter(s)") + error = ( + TargetdError.INVALID_PARMETER, + "invalid method parameter(s)") log.debug(traceback.format_exc()) raise except TargetdError, td: @@ -185,7 +187,7 @@ def load_config(config_path): # convert log level to int config['log_level'] = getattr(log, config['log_level'].upper(), log.INFO) log.basicConfig(level=config['log_level']) - +
def update_mapping(): # wait until now so submodules can import 'main' safely diff --git a/targetd/utils.py b/targetd/utils.py index 7150eba..522f445 100644 --- a/targetd/utils.py +++ b/targetd/utils.py @@ -17,6 +17,7 @@
from subprocess import Popen, PIPE from contextlib import contextmanager +import re
@contextmanager def ignored(*exceptions): @@ -26,7 +27,22 @@ def ignored(*exceptions): pass
+_NAME_REGEX = '^[a-zA-Z0-9_-]+$' + + +def name_check(name): + if not re.match(_NAME_REGEX, name): + raise TargetdError( + TargetdError.INVALID_ARGUMENT, + "Illegal name, should match: %s" % _NAME_REGEX) + + class TargetdError(Exception): + INVALID_ARGUMENT = -32602 + NO_SUPPORT = -153 + NAME_CONFLICT = -50 + EXISTS_INITIATOR = -52 + def __init__(self, error_code, *args, **kwargs): Exception.__init__(self, *args, **kwargs) self.error = error_code
* New method name_check() in utils.py to enforce name API document definition in 'Conventions' section: '[a-z][A-Z][0-9]_-'
* New constants: TargetdError.INVALID_ARGUMENT TargetdError.NO_SUPPORT TargetdError.NAME_CONFLICT TargetdError.EXISTS_INITIATOR
* New public method: access_group_create(ag_name, init_id, init_type): Create new access group. Parameters: ag_name (str): Access group name init_id (str): iSCSI initiator address init_type (str): Reserved for future use. Should be set as 'iscsi' Returns: N/A Errors: -32602: Invalid parameter. Provided ag_name is illegal. Check 'Conventions' for detail. -153: No support. The 'init_type' is not 'iscsi' -50: Name conflict. Requested 'ag_name' is in use -52: Exists initiator. Requested 'init_id' is in use
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 16 ++++++++++++++++ targetd/block.py | 34 ++++++++++++++++++++++++++++++++-- targetd/main.py | 6 ++++-- targetd/utils.py | 16 ++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-)
diff --git a/API.md b/API.md index 56044ad..cb737b3 100644 --- a/API.md +++ b/API.md @@ -149,6 +149,22 @@ Returns: Errors: N/A
+### access_group_create(ag_name, init_id, init_type) +Create new access group. + +Parameters: + ag_name (str): Access group name + init_id (str): iSCSI initiator address + init_type (str): Reserved for future use. Should be set as 'iscsi' +Returns: + N/A +Errors: + -32602: Invalid parameter. Provided 'ag_name' is illegal. Check + 'Conventions' for detail. + -153: No support. The 'init_type' is not 'iscsi' + -50: Name conflict. Requested 'ag_name' is in use + -52: Exists initiator. Requested 'init_id' is in use + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index 2e185ef..71c843b 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -18,10 +18,10 @@ import contextlib from rtslib_fb import ( Target, TPG, NodeACL, FabricModule, BlockStorageObject, RTSRoot, - NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS) + NetworkPortal, LUN, MappedLUN, RTSLibError, RTSLibNotInCFS, NodeACLGroup) import lvm from main import TargetdError -from utils import ignored +from utils import ignored, name_check
def get_vg_lv(pool_name): @@ -108,6 +108,7 @@ def initialize(config_dict): initiator_set_auth=initiator_set_auth, initiator_list=initiator_list, access_group_list=access_group_list, + access_group_create=access_group_create, )
@@ -397,3 +398,32 @@ def access_group_list(req): 'init_type': 'iscsi', } for node_acl_group in _get_iscsi_tpg().node_acl_groups) + + +def access_group_create(req, ag_name, init_id, init_type): + if init_type != 'iscsi': + raise TargetdError( + TargetdError.NO_SUPPORT, "Only support iscsi") + + name_check(ag_name) + + tpg = _get_iscsi_tpg() + + # Pre-check: + # 1. Name conflict: requested name is in use + # 2. Initiator conflict: request initiator is in use + + 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(i.node_wwn for i in tpg.node_acls): + raise TargetdError( + TargetdError.EXISTS_INITIATOR, + "Requested init_id is in use") + + node_acl_group = NodeACLGroup(tpg, ag_name) + node_acl_group.add_acl(init_id) + RTSRoot().save_to_file() diff --git a/targetd/main.py b/targetd/main.py index 8af5300..04aff5e 100644 --- a/targetd/main.py +++ b/targetd/main.py @@ -113,7 +113,9 @@ class TargetHandler(BaseHTTPRequestHandler): log.debug(traceback.format_exc()) raise except TypeError: - error = (-32602, "invalid method parameter(s)") + error = ( + TargetdError.INVALID_PARMETER, + "invalid method parameter(s)") log.debug(traceback.format_exc()) raise except TargetdError, td: @@ -185,7 +187,7 @@ def load_config(config_path): # convert log level to int config['log_level'] = getattr(log, config['log_level'].upper(), log.INFO) log.basicConfig(level=config['log_level']) - +
def update_mapping(): # wait until now so submodules can import 'main' safely diff --git a/targetd/utils.py b/targetd/utils.py index 7150eba..522f445 100644 --- a/targetd/utils.py +++ b/targetd/utils.py @@ -17,6 +17,7 @@
from subprocess import Popen, PIPE from contextlib import contextmanager +import re
@contextmanager def ignored(*exceptions): @@ -26,7 +27,22 @@ def ignored(*exceptions): pass
+_NAME_REGEX = '^[a-zA-Z0-9_-]+$' + + +def name_check(name): + if not re.match(_NAME_REGEX, name): + raise TargetdError( + TargetdError.INVALID_ARGUMENT, + "Illegal name, should match: %s" % _NAME_REGEX) + + class TargetdError(Exception): + INVALID_ARGUMENT = -32602 + NO_SUPPORT = -153 + NAME_CONFLICT = -50 + EXISTS_INITIATOR = -52 + def __init__(self, error_code, *args, **kwargs): Exception.__init__(self, *args, **kwargs) self.error = error_code
* New method: access_group_destroy(ag_name) Delete a access group including it's initiator and volume masking status. No error will be raised even provided access group name does not exist. Parameters: ag_name (str): Access group name Returns: N/A Errors: N/A
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 10 ++++++++++ targetd/block.py | 6 ++++++ 2 files changed, 16 insertions(+)
diff --git a/API.md b/API.md index cb737b3..214f636 100644 --- a/API.md +++ b/API.md @@ -165,6 +165,16 @@ Errors: -50: Name conflict. Requested 'ag_name' is in use -52: Exists initiator. Requested 'init_id' is in use
+### access_group_destroy(ag_name) +Delete a access group including it's initiator and volume masking status. +No error will be raised even provided access group name does not exist. +Parameters: + ag_name (str): Access group name +Returns: + N/A +Errors: + N/A + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index 71c843b..e834a09 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -109,6 +109,7 @@ def initialize(config_dict): initiator_list=initiator_list, access_group_list=access_group_list, access_group_create=access_group_create, + access_group_destroy=access_group_destroy, )
@@ -427,3 +428,8 @@ def access_group_create(req, ag_name, init_id, init_type): node_acl_group = NodeACLGroup(tpg, ag_name) node_acl_group.add_acl(init_id) RTSRoot().save_to_file() + + +def access_group_destroy(req, ag_name): + NodeACLGroup(_get_iscsi_tpg(), ag_name).delete() + RTSRoot().save_to_file()
* New method: access_group_init_add(ag_name, init_id, init_type) Add a new initiator into a access group. If defined access group does not exist, create one with requested initiator. Parameters: ag_name (str): Access group name init_id (str): iSCSI initiator address init_type (str): Reserved for future use. Should be set as 'iscsi' Returns: N/A Errors: -52: Exists initiator. Requested 'init_id' is in use
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 12 ++++++++++++ targetd/block.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+)
diff --git a/API.md b/API.md index 214f636..c16eb54 100644 --- a/API.md +++ b/API.md @@ -175,6 +175,18 @@ Returns: Errors: N/A
+### access_group_init_add(ag_name, init_id, init_type) +Add a new initiator into a access group. +If defined access group does not exist, create one with requested initiator. +Parameters: + ag_name (str): Access group name + init_id (str): iSCSI initiator address + init_type (str): Reserved for future use. Should be set as 'iscsi' +Returns: + N/A +Errors: + -52: Exists initiator. Requested 'init_id' is in use + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index e834a09..0ea7875 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -110,6 +110,7 @@ def initialize(config_dict): access_group_list=access_group_list, access_group_create=access_group_create, access_group_destroy=access_group_destroy, + access_group_init_add=access_group_init_add, )
@@ -433,3 +434,32 @@ def access_group_create(req, ag_name, init_id, init_type): def access_group_destroy(req, ag_name): NodeACLGroup(_get_iscsi_tpg(), ag_name).delete() 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. Already in requested access group, return silently. + # 2. Initiator does not exist. + # 3. Initiator not used by other access group. + + if init_id in list(NodeACLGroup(tpg, ag_name).wwns): + return + + for node_acl_group in tpg.node_acl_groups: + if init_id in list(node_acl_group.wwns): + raise TargetdError( + TargetdError.EXISTS_INITIATOR, + "Requested init_id is used by other access group") + for node_acl in tpg.node_acls: + if init_id == node_acl.node_wwn: + raise TargetdError( + TargetdError.EXISTS_INITIATOR, + "Requested init_id is in use") + + NodeACLGroup(tpg, ag_name).add_acl(init_id) + RTSRoot().save_to_file()
* access_group_init_del(ag_name, init_id, init_type) Remove a initiator from an access group. If requested initiator not in defined access group, return silently. If defined access group does not exist, return silently. Parameters: ag_name (str): Access group name init_id (str): iSCSI initiator address init_type (str): Reserved for future use. Should be set as 'iscsi' Returns: N/A Errors: N/A
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 13 +++++++++++++ targetd/block.py | 17 +++++++++++++++++ 2 files changed, 30 insertions(+)
diff --git a/API.md b/API.md index c16eb54..52c0f4c 100644 --- a/API.md +++ b/API.md @@ -187,6 +187,19 @@ Returns: Errors: -52: Exists initiator. Requested 'init_id' is in use
+### access_group_init_del(ag_name, init_id, init_type) +Remove a initiator from an access group. +If requested initiator not in defined access group, return silently. +If defined access group does not exist, return silently. +Parameters: + ag_name (str): Access group name + init_id (str): iSCSI initiator address + init_type (str): Reserved for future use. Should be set as 'iscsi' +Returns: + N/A +Errors: + N/A + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index 0ea7875..bdf2332 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -111,6 +111,7 @@ def initialize(config_dict): access_group_create=access_group_create, access_group_destroy=access_group_destroy, access_group_init_add=access_group_init_add, + access_group_init_del=access_group_init_del, )
@@ -463,3 +464,19 @@ def access_group_init_add(req, ag_name, init_id, init_type):
NodeACLGroup(tpg, ag_name).add_acl(init_id) RTSRoot().save_to_file() + + +def access_group_init_del(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 is not in requested access group, return silently. + if init_id not in list(NodeACLGroup(tpg, ag_name).wwns): + return + + NodeACLGroup(tpg, ag_name).remove_acl(init_id) + RTSRoot().save_to_file()
* New method: access_group_map_list() Query volume mapping status of all access groups. Parameters: N/A Returns: [ { 'ag_name': str, 'h_lun_id': int, 'pool_name': str, 'vol_name': str, } ] The 'ag_name' is the name of access group. The 'h_lun_id' is the SCSI LUN ID seen by iSCSI initiator. The 'pool_name' is the name of pool which volume is belonging to. The 'vol_name' is the name of volume. Errors: N/A
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 20 ++++++++++++++++++++ targetd/block.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+)
diff --git a/API.md b/API.md index 52c0f4c..2c9b064 100644 --- a/API.md +++ b/API.md @@ -200,6 +200,26 @@ Returns: Errors: N/A
+### access_group_map_list() +Query volume mapping status of all access groups. +Parameters: + N/A +Returns: + [ + { + 'ag_name': str, + 'h_lun_id': int, + 'pool_name': str, + 'vol_name': str, + } + ] + The 'ag_name' is the name of access group. + The 'h_lun_id' is the SCSI LUN ID seen by iSCSI initiator. + The 'pool_name' is the name of pool which volume is belonging to. + The 'vol_name' is the name of volume. +Errors: + N/A + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index bdf2332..162c84e 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -112,6 +112,7 @@ def initialize(config_dict): access_group_destroy=access_group_destroy, access_group_init_add=access_group_init_add, access_group_init_del=access_group_init_del, + access_group_map_list=access_group_map_list, )
@@ -480,3 +481,40 @@ def access_group_init_del(req, ag_name, init_id, init_type):
NodeACLGroup(tpg, ag_name).remove_acl(init_id) 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 + '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_lun_groups: + tpg_lun = mapped_lun_group.tpg_lun + so_name = tpg_lun.storage_object.name + (vg_name, vol_name) = so_name.split(":") + # When user delete old volume and the created new one with + # idential name. The mapping status will be kept. + # Hence we don't expose volume UUID here. + results.append( + { + 'ag_name': node_acl_group.name, + 'h_lun_id': mapped_lun_group.mapped_lun, + 'pool_name': vg_name_2_pool_name_dict[vg_name], + 'vol_name': vol_name, + } + ) + + return results
* New method: access_group_map_create(pool_name, vol_name, ag_name, h_lun_id=None): Grant certain access group the rw access to defined volume. Parameters: pool_name (str): The name of pool which defined volume belongs to. vol_name (str): The name of volume. ag_name (str): Access group name h_lun_id (int, optional): Host LUN ID (the SCSI LUN ID seen by iSCSI initiator). Range is 0 to 255. If not defined, targetd will try to find the next available one. Returns: N/A Errors: -1000: No free host_lun_id. LUN ID between 0 ~ 255 is in use. -200: Access group not found.
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 16 +++++++++ targetd/block.py | 105 +++++++++++++++++++++++++++++++++++++++++-------------- targetd/utils.py | 2 ++ 3 files changed, 97 insertions(+), 26 deletions(-)
diff --git a/API.md b/API.md index 2c9b064..6fe769c 100644 --- a/API.md +++ b/API.md @@ -220,6 +220,22 @@ Returns: Errors: N/A
+### access_group_map_create(pool_name, vol_name, ag_name, h_lun_id=None) +Grant certain access group the rw access to defined volume. +Parameters: + pool_name (str): The name of pool which defined volume belongs to. + vol_name (str): The name of volume. + ag_name (str): Access group name + h_lun_id (int, optional): + Host LUN ID (the SCSI LUN ID seen by iSCSI initiator). + Range is 0 to 255. + If not defined, targetd will try to find the next available one. +Returns: + N/A +Errors: + -1000: No free host_lun_id. LUN ID between 0 ~ 255 is in use. + -200: Access group not found. + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index 162c84e..3695f65 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -113,6 +113,7 @@ def initialize(config_dict): access_group_init_add=access_group_init_add, access_group_init_del=access_group_init_del, access_group_map_list=access_group_map_list, + access_group_map_create=access_group_map_create, )
@@ -201,24 +202,6 @@ def export_list(req):
def export_create(req, pool, vol, initiator_wwn, lun): - # get wwn of volume so LIO can export as vpd83 info - vg_name, thin_pool = get_vg_lv(pool) - - with vgopen(vg_name) as vg: - vol_serial = vg.lvFromName(vol).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) - try: - so = BlockStorageObject(so_name) - except RTSLibError: - so = BlockStorageObject(so_name, dev="/dev/%s/%s" % (vg_name, vol)) - so.wwn = vol_serial - - # export useful scsi model if kernel > 3.8 - with ignored(RTSLibError): - so.set_attribute("emulate_model_alias", '1')
fm = FabricModule('iscsi') t = Target(fm, target_name) @@ -228,14 +211,7 @@ 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) + 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: @@ -518,3 +494,80 @@ def access_group_map_list(req): )
return results + + +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_name) + + with vgopen(vg_name) as vg: + 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_name) + try: + so = BlockStorageObject(so_name) + except RTSLibError: + so = BlockStorageObject( + so_name, dev="/dev/%s/%s" % (vg_name, vol_name)) + so.wwn = vol_serial + + # export useful scsi model if kernel > 3.8 + 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 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 = NodeACLGroup(tpg, ag_name) + if len(list(node_acl_group.wwns)) == 0: + # Non-exist access group means volume mapping status will not be + # stored. This should be considered as an error instead of sliently + # return. + raise TargetdError( + TargetdError.NOT_FOUND_ACCESS_GROUP, "Access group not found") + + 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_group(h_lun_id, tpg_lun) + RTSRoot().save_to_file() diff --git a/targetd/utils.py b/targetd/utils.py index 522f445..e2b82ae 100644 --- a/targetd/utils.py +++ b/targetd/utils.py @@ -42,6 +42,8 @@ class TargetdError(Exception): NO_SUPPORT = -153 NAME_CONFLICT = -50 EXISTS_INITIATOR = -52 + NO_FREE_HOST_LUN_ID = -1000 + NOT_FOUND_ACCESS_GROUP = -200
def __init__(self, error_code, *args, **kwargs): Exception.__init__(self, *args, **kwargs)
* New method access_group_map_destroy(pool_name, vol_name, ag_name) Revoke the rw access of certain access group to defined volume. Parameters: pool_name (str): The name of pool which defined volume belongs to. vol_name (str): The name of volume. ag_name (str): Access group name Returns: N/A Errors: N/A
Signed-off-by: Gris Ge fge@redhat.com --- API.md | 11 +++++++++++ targetd/block.py | 12 ++++++++++++ 2 files changed, 23 insertions(+)
diff --git a/API.md b/API.md index 6fe769c..8a720ba 100644 --- a/API.md +++ b/API.md @@ -236,6 +236,17 @@ Errors: -1000: No free host_lun_id. LUN ID between 0 ~ 255 is in use. -200: Access group not found.
+### access_group_map_destroy(pool_name, vol_name, ag_name) +Revoke the rw access of certain access group to defined volume. +Parameters: + pool_name (str): The name of pool which defined volume belongs to. + vol_name (str): The name of volume. + ag_name (str): Access group name +Returns: + N/A +Errors: + N/A + File system operations ---------------------- Ability to create different file systems and perform operation on them. The diff --git a/targetd/block.py b/targetd/block.py index 3695f65..888e6e4 100644 --- a/targetd/block.py +++ b/targetd/block.py @@ -114,6 +114,7 @@ def initialize(config_dict): access_group_init_del=access_group_init_del, access_group_map_list=access_group_map_list, access_group_map_create=access_group_map_create, + access_group_map_destroy=access_group_map_destroy, )
@@ -571,3 +572,14 @@ def access_group_map_create(req, pool_name, vol_name, ag_name, h_lun_id=None):
node_acl_group.mapped_lun_group(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 = NodeACLGroup(tpg, ag_name) + tpg_lun = _tpg_lun_of(tpg, pool_name, vol_name) + for map_group in node_acl_group.mapped_lun_groups: + if map_group.tpg_lun == tpg_lun: + map_group.delete() + + RTSRoot().save_to_file()
On 01/26/2015 06:14 AM, Gris Ge wrote:
To test this patch set:
- Install rtslib-fb at commit 27e9a1df9c004862fedea4786838a4e4acb8ef94 cp <git_folder>/rtslib /usr/lib/python2.7/site-packages/rtslib_fb
- Apply this patch set.
- Restart targetd.
- Apply this patch set to libstoragemgmt developer tree[1]: [PATCH 0/6] Targetd Plugin: Introduce full access group support
- Test these commands: lsmcli ac --name gris_ag_03 --init iqn.2000-04.com.redhat:gris.03 \ --sys targetd lsmcli aa --ag gris_ag_03 --init iqn.2000-04.com.redhat:gris.13 lsmcli ar --ag gris_ag_03 --init iqn.2000-04.com.redhat:gris.13 lsmcli vm --vol gQTwyb-PoV5-NxWw-gMn1-0u7X-2tGp-8iaV0A --ag gris_ag_03 lsmcli vu --vol gQTwyb-PoV5-NxWw-gMn1-0u7X-2tGp-8iaV0A --ag gris_ag_03 lsmcli ad --ag gris_ag_03
Tested against libstoragemgmt targetd plugin patch.
Let me know if your prefer I submit this via github pull request.
[1] b933bccad3ee29538f6200ae1b26e3644e9358fe
Gris Ge (9): New method: initiator_list(standalone_only=False) New method: access_group_list() New method: access_group_create() New method: access_group_destroy(ag_name) New method: access_group_init_add(ag_name, init_id, init_type) New method: access_group_init_del(ag_name, init_id, init_type) New method: access_group_map_list() New method: access_group_map_create(pool_name, vol_name, ag_name, h_lun_id=None) New method: access_group_map_destroy(pool_name, vol_name, ag_name)
API.md | 148 +++++++++++++++++++++++++- targetd/block.py | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++----- targetd/main.py | 6 +- targetd/utils.py | 18 ++++ 4 files changed, 455 insertions(+), 32 deletions(-)
Great work.
I've applied this patchset and will look to tag a new release later this week.
Thanks! -- Regards -- Andy
targetd-devel@lists.fedorahosted.org