Discussion:
[Libstoragemgmt-devel] [PATCH 1/2] Python library: Add some error code for volume masking
Gris Ge
2014-07-28 14:16:11 UTC
Permalink
To simplify LSM user and plugin implementation, I would like to restrict
these:
1. access_group_initiator_delete()
Refuse to delete the last initiator from access group which has
volume masked.
Some plugin(SMI-S for EMC VMAX) does not allow keeping volume masking
information when no member in access group.

2. volume_mask()
Refuse to do volume masking against empty(no member) access group.
The same reason as previous one.

3. access_group_delete():
Refuse to delete a access group which has volume masked.

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm/_common.py | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index 1f52542..fdd10ab 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
@@ -514,9 +514,17 @@ class ErrorNumber(object):

DISK_BUSY = 500
VOLUME_BUSY = 501
+ ACCESS_GROUP_BUSY = 502 # refuse to remove the last initiator from
+ # access group which have volume masked.

UNSUPPORTED_SEARCH_KEY = 510

+ EMPTY_ACCESS_GROUP = 511 # volume_mask() will fail if access group
+ # has no member/initiator.
+
+ MASKED_ACCESS_GROUP = 512 # access_group_delete() will fail if
+ # have volume masked.
+
_LOCALS = locals()

@staticmethod
--
1.8.3.1
Gris Ge
2014-07-28 14:16:12 UTC
Permalink
Big changes:
1. Access Group could from one of these CIM class:
CIM_SCSIProtocolController
# For fallback or only support 'Masking and Mapping' profile
# arrays. In real world, it means most of arrays.
CIM_InitiatorMaskingGroup
# For 1.5+ 'Group Masking and Mapping' profile.
# In real world, it means EMC VMAX only.
# A EMC Bug found on EMC VNX/CX which incorrect indicate
# support of Group M&M profile, bug reported and work around
# implemented.
# The reason to do this is because EMC VMAX does not allow to create
# SPC with no volume masked.
# Actually, 'Group M&M' profile is clearer and easier for use than
# old 'M&M' profile. I will try to beg vendors to support that profile.

2. Fix old access_group_initiator_add() and
access_group_initiator_delete() to return None instead of job id.

3. New methods:
access_group_create()
access_group_delete()

Detail changes:
1. Use _mask_type() to indicate the support of Group M&M.
2. These methods support both 'Group' way and old way:
volume_mask()
volume_unmask()
access_groups()
access_group_initiator_add()
access_group_initiator_delete()
3. Quick notes for 'Group M&M' profile:
SPC(SCSIProtocolController) contain three groups:
a InitiatorMaskingGroup
# Containing Initiators.
b TargetMaskingGroup
# Containing target ports. Currently we expose to all ports.
c DeviceMaskingGroup
# Containing masked volumes.

4. New method _wait_invoke() to allow wait cim_job done.
5. Remove support of this method on EMC VNX/CX:
access_group_initiator_add()
# EMC VNX/CX require WWNN define when add initiator which is not
# supported by LSM yet. We have no intention to do so, WWPN should
# the only identifier for FC and FCoE, we should not confuse user
# with one vendor extension.

Tested on:
1. EMC VMAX:
volume_mask()
volume_unmask()
access_groups()
access_group_initiator_add()
access_group_initiator_delete()
access_group_create()
access_group_delete()
2. EMC VNX:
volume_mask()
volume_unmask()
access_groups()

TODO:
* I will submit another patch soon for lsm.Client.capabilities() call about
access group.
* The SMI-S plugin is too big to maintain. We(at least I) should split it
into several files/modules.


Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/smispy/smis.py | 1680 ++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 1366 insertions(+), 314 deletions(-)

diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index e77e21e..5c0beac 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -20,6 +20,7 @@
from string import split
import time
import traceback
+import copy

import pywbem
from pywbem import CIMError
@@ -38,13 +39,17 @@ from lsm import (IStorageAreaNetwork, error, uri_parse, LsmError, ErrorNumber,
# cim_vol CIM_StorageVolume
# cim_rp CIM_RegisteredProfile
# cim_init CIM_StorageHardwareID
-# cim_ag CIM_SCSIProtocolController
+# cim_spc CIM_SCSIProtocolController
+# cim_init_mg CIM_InitiatorMaskingGroup
# cim_fc_tgt CIM_FCPort
# cim_iscsi_pg CIM_iSCSIProtocolEndpoint # iSCSI portal group
# cim_iscsi_node CIM_SCSIProtocolController
# cim_tcp CIM_TCPProtocolEndpoint,
# cim_ip CIM_IPProtocolEndpoint
# cim_eth CIM_EthernetPort
+# cim_pe CIM_SCSIProtocolEndpoint
+# cim_gmm CIM_GroupMaskingMappingService
+# cim_ccs CIM_ControllerConfigurationService
#
# sys Object of LSM System
# pool Object of LSM Pool
@@ -70,13 +75,23 @@ def handle_cim_errors(method):
except CIMError as ce:
error_code, desc = ce

- if error_code == 0 and 'Socket error' in desc:
- if 'Errno 111' in desc:
- raise LsmError(ErrorNumber.NETWORK_CONNREFUSED,
- 'Connection refused')
- if 'Errno 113' in desc:
- raise LsmError(ErrorNumber.NETWORK_HOSTDOWN,
- 'Host is down')
+ if error_code == 0:
+ if 'Socket error' in desc:
+ if 'Errno 111' in desc:
+ raise LsmError(ErrorNumber.NETWORK_CONNREFUSED,
+ 'Connection refused')
+ if 'Errno 113' in desc:
+ raise LsmError(ErrorNumber.NETWORK_HOSTDOWN,
+ 'Host is down')
+ elif 'SSL error' in desc:
+ raise LsmError(ErrorNumber.TRANSPORT_COMMUNICATION,
+ desc)
+ elif 'The web server returned a bad status line':
+ raise LsmError(ErrorNumber.TRANSPORT_COMMUNICATION,
+ desc)
+ elif 'HTTP error' in desc:
+ raise LsmError(ErrorNumber.TRANSPORT_COMMUNICATION,
+ desc)
raise LsmError(ErrorNumber.LSM_PLUGIN_BUG, desc)
except pywbem.cim_http.AuthError as ae:
raise LsmError(ErrorNumber.PLUGIN_AUTH_FAILED, "Unauthorized user")
@@ -148,6 +163,22 @@ class DMTF(object):
# ULA: Unique Local Address, aka Site Local Unicast.
# fc00::/7

+ # CIM_GroupMaskingMappingService.CreateGroup('Type')
+ MASK_GROUP_TYPE_INIT = pywbem.Uint16(2)
+ MASK_GROUP_TYPE_TGT = pywbem.Uint16(3)
+ MASK_GROUP_TYPE_DEV = pywbem.Uint16(4)
+
+ # CIM_GroupMaskingMappingCapabilities['SupportedDeviceGroupFeatures']
+ # Allowing empty DeviceMaskingGroup associated to SPC
+ GMM_CAP_DEV_MG_ALLOW_EMPTY_W_SPC = pywbem.Uint16(5)
+
+ # CIM_GroupMaskingMappingCapabilities['SupportedInitiatorGroupFeatures']
+ # Allowing empty DeviceMaskingGroup
+ GMM_CAP_INIT_MG_ALLOW_EMPTY = pywbem.Uint16(4)
+ # Allowing empty DeviceMaskingGroup associated to SPC
+ GMM_CAP_INIT_MG_ALLOW_EMPTY_W_SPC = pywbem.Uint16(5)
+
+

_INIT_TYPE_CONV = {
DMTF.ID_TYPE_OTHER: AccessGroup.INIT_TYPE_OTHER,
@@ -205,6 +236,7 @@ class SNIA(object):
DISK_LITE_PROFILE = 'Disk Drive Lite'
MULTI_SYS_PROFILE = 'Multiple Computer System'
MASK_PROFILE = 'Masking and Mapping'
+ GROUP_MASK_PROFILE = 'Group Masking and Mapping'
FC_TGT_PORT_PROFILE = 'FC Target Ports'
ISCSI_TGT_PORT_PROFILE = 'iSCSI Target Ports'
SMIS_SPEC_VER_1_4 = '1.4'
@@ -404,6 +436,13 @@ class Smis(IStorageAreaNetwork):
IAAN_WBEM_HTTP_PORT = 5988
IAAN_WBEM_HTTPS_PORT = 5989

+ MASK_TYPE_NO_SUPPORT = 0
+ MASK_TYPE_MASK = 1
+ MASK_TYPE_GROUP = 2
+
+ _INVOKE_MAX_LOOP_COUNT = 60
+ _INVOKE_CHECK_INTERVAL = 5
+
class RepSvc(object):

class Action(object):
@@ -1046,13 +1085,11 @@ class Smis(IStorageAreaNetwork):
rc = ['SystemName', 'DeviceID']
elif class_type == 'Job':
rc = ['InstanceID']
- elif class_type == 'AccessGroup':
- rc = ['DeviceID']
elif class_type == 'Initiator':
rc = ['StorageID']
else:
raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
- "Smis._cim_class_name_of() got unknown " +
+ "Smis._property_list_of_id() got unknown " +
"class_type %s" % class_type)

if extra_properties:
@@ -1099,12 +1136,6 @@ class Smis(IStorageAreaNetwork):
"""
return "%s@%d" % (self._id('Job', cim_job), int(retrieve_data))

- def _access_group_id(self, cim_ag):
- """
- Retrive Access Group ID from CIM_SCSIProtocolController['DeviceID']
- """
- return self._id('AccessGroup', cim_ag)
-
def _init_id(self, cim_init):
"""
Retrive Initiator ID from CIM_StorageHardwareID
@@ -1189,7 +1220,7 @@ class Smis(IStorageAreaNetwork):

return other_id

- def _new_vol_cim_vol_pros(self):
+ def _cim_vol_pros(self):
"""
Retrun the PropertyList required for creating new LSM Volume.
"""
@@ -1381,26 +1412,44 @@ class Smis(IStorageAreaNetwork):
"Got not new Pool from out of InvokeMethod" +
"when CreateOrModifyElementFromStoragePool")

- def _cim_ag_pros(self):
+ def _cim_spc_pros(self):
"""
Return a list of properties required to build new AccessGroup.
"""
- cim_ag_pros = self._property_list_of_id('AccessGroup')
- cim_ag_pros.extend(self._property_list_of_id('SystemChild'))
- cim_ag_pros.extend(['ElementName', 'StorageID'])
- cim_ag_pros.extend(['EMCAdapterRole']) # EMC specific, used to
+ cim_spc_pros = ['DeviceID']
+ cim_spc_pros.extend(self._property_list_of_id('SystemChild'))
+ cim_spc_pros.extend(['ElementName', 'StorageID'])
+ cim_spc_pros.extend(['EMCAdapterRole']) # EMC specific, used to
# filter out the mapping SPC.
- return cim_ag_pros
+ return cim_spc_pros
+
+ @staticmethod
+ def _init_type_of_cim_inits(cim_inits):
+ ag_init_types = [_dmtf_init_type_to_lsm(i) for i in cim_inits]
+ init_type = AccessGroup.INIT_TYPE_UNKNOWN
+ ag_init_type_dict = {}
+ for ag_init_type in ag_init_types:
+ ag_init_type_dict[ag_init_type] = 1
+ if len(ag_init_type_dict) == 1:
+ init_type = ag_init_types[0]
+ elif (len(ag_init_type_dict) == 2 and
+ AccessGroup.INIT_TYPE_ISCSI_IQN in ag_init_type_dict.keys() and
+ AccessGroup.INIT_TYPE_WWPN in ag_init_type_dict.keys()):
+ init_type = AccessGroup.INIT_TYPE_ISCSI_WWPN_MIXED
+ else:
+ # We have unknown mixed initiator type
+ init_type = AccessGroup.INIT_TYPE_OTHER
+ return init_type

- def _cim_ag_to_lsm(self, cim_ag, system_id=None):
+ def _cim_spc_to_lsm(self, cim_spc, system_id=None):
if system_id is None:
- system_id = self._sys_id_child(cim_ag)
- ag_id = self._access_group_id(cim_ag)
- ag_name = cim_ag['ElementName']
+ system_id = self._sys_id_child(cim_spc)
+ ag_id = md5(cim_spc['DeviceID'])
+ ag_name = cim_spc['ElementName']
ag_init_ids = []
cim_init_pros = self._property_list_of_id('Initiator')
cim_init_pros.extend(['IDType'])
- cim_inits = self._cim_init_of(cim_ag, cim_init_pros)
+ cim_inits = self._cim_init_of_spc(cim_spc.path, cim_init_pros)
ag_init_ids = [self._init_id(i) for i in cim_inits]
ag_init_types = [_dmtf_init_type_to_lsm(i) for i in cim_inits]
init_type = AccessGroup.INIT_TYPE_UNKNOWN
@@ -1417,7 +1466,7 @@ class Smis(IStorageAreaNetwork):
# We have unknown mixed initiator type
init_type = AccessGroup.INIT_TYPE_OTHER

- sys_id = self._sys_id_child(cim_ag)
+ sys_id = self._sys_id_child(cim_spc)
return AccessGroup(ag_id, ag_name, ag_init_ids, init_type, sys_id)

def _new_vol_from_job(self, job):
@@ -1465,7 +1514,7 @@ class Smis(IStorageAreaNetwork):
rc = []
cim_sys_pros = self._property_list_of_id("System")
cim_syss = self._root_cim_syss(cim_sys_pros)
- cim_vol_pros = self._new_vol_cim_vol_pros()
+ cim_vol_pros = self._cim_vol_pros()
for cim_sys in cim_syss:
sys_id = self._sys_id(cim_sys)
pool_pros = self._property_list_of_id('Pool')
@@ -1934,83 +1983,412 @@ class Smis(IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return None

+ def _get_cim_service_path(self, cim_sys_path, class_name):
+ """
+ Return None if not supported
+ """
+ try:
+ cim_srvs = self._c.AssociatorNames(
+ cim_sys_path,
+ AssocClass='CIM_HostedService',
+ ResultClass=class_name)
+ except CIMError as ce:
+ if ce[0] == pywbem.CIM_ERR_NOT_SUPPORTED:
+ return None
+ else:
+ raise
+ if len(cim_srvs) == 1:
+ return cim_srvs[0]
+ elif len(cim_srvs) == 0:
+ return None
+ else:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_get_cim_service_path(): Got unexpected(not 1) "
+ "count of %s from cim_sys %s: %s" %
+ (class_name, cim_sys_path, cim_srvs))
+
+ def _cim_dev_mg_path_create(self, cim_gmm_path, name, cim_vol_path, vol_id):
+ in_params = {
+ 'GroupName': name,
+ 'Members': [cim_vol_path],
+ 'Type': DMTF.MASK_GROUP_TYPE_DEV}
+
+ cim_dev_mg_path = None
+ try:
+ (rc, out) = self._c.InvokeMethod('CreateGroup', cim_gmm_path,
+ **in_params)
+ except CIMError as ce:
+ if ce[0] == pywbem.CIM_ERR_FAILED:
+ cim_dev_mg_path = self._check_exist_cim_dev_mg(
+ name, cim_gmm_path, cim_vol_path, vol_id)
+ if cim_dev_mg_path is None:
+ raise
+ else:
+ raise
+ if cim_dev_mg_path is None:
+ cim_dev_mg_path = self._wait_invoke(
+ rc, out, out_key='MaskingGroup',
+ expect_class='CIM_TargetMaskingGroup')
+
+ return cim_dev_mg_path
+
+
+ def _cim_tgt_mg_path_create(self, cim_sys_path, cim_gmm_path, name,
+ init_type):
+ """
+ Create CIM_TargetMaskingGroup
+ Currently, LSM does not support target ports masking
+ we will mask to all target ports.
+ Return CIMInstanceName of CIM_TargetMaskingGroup
+ """
+ in_params = {
+ 'GroupName': name,
+ 'Type': DMTF.MASK_GROUP_TYPE_TGT}
+
+ if init_type == AccessGroup.INIT_TYPE_WWPN:
+ cim_fc_tgts = self._cim_fc_tgt_of(cim_sys_path)
+ all_cim_fc_peps_path = []
+ all_cim_fc_peps_path.extend(
+ [self._cim_pep_path_of_fc_tgt(x.path) for x in cim_fc_tgts])
+ in_params['Members'] = all_cim_fc_peps_path
+
+ elif init_type == AccessGroup.INIT_TYPE_ISCSI_IQN:
+ cim_iscsi_pgs = self._cim_iscsi_pg_of(cim_sys_path)
+ in_params['Members'] = [x.path for x in cim_iscsi_pgs]
+ else:
+ # Already checked at the begining of this method
+ pass
+
+ cim_tgt_mg_path = None
+ try:
+ (rc, out) = self._c.InvokeMethod('CreateGroup', cim_gmm_path,
+ **in_params)
+ except CIMError as ce:
+ if ce[0] == pywbem.CIM_ERR_FAILED:
+ cim_tgt_mg_path = self._check_exist_cim_tgt_mg(name)
+ if cim_tgt_mg_path is None:
+ raise
+ else:
+ raise
+
+ if cim_tgt_mg_path is None:
+ cim_tgt_mg_path = self._wait_invoke(
+ rc, out, out_key='MaskingGroup',
+ expect_class='CIM_TargetMaskingGroup')
+
+ return cim_tgt_mg_path
+
+ def _cim_spc_path_create(self, cim_gmm_path, cim_init_mg_path,
+ cim_tgt_mg_path, cim_dev_mg_path, name):
+ in_params = {
+ 'ElementName': name,
+ 'InitiatorMaskingGroup': cim_init_mg_path,
+ 'TargetMaskingGroup': cim_tgt_mg_path,
+ 'DeviceMaskingGroup': cim_dev_mg_path,
+ }
+
+ (rc, out) = self._c.InvokeMethod('CreateMaskingView', cim_gmm_path,
+ **in_params)
+
+ return self._wait_invoke(
+ rc, out, out_key='ProtocolController',
+ expect_class='CIM_SCSIProtocolController')
+
+ def _volume_mask_group(self, access_group, volume, flags=0):
+ """
+ Grant access to a volume to an group
+ Use GroupMaskingMappingService.AddMembers() for Group Masking
+ Use ControllerConfigurationService.ExposePaths() for Masking.
+ Currently, LSM does not have a way to control which target port to
+ mask.
+ If CIM_TargetMaskingGroup already defined for current
+ CIM_InitiatorMaskingGroup, we use that.
+ If No CIM_TargetMaskingGroup exist, we create one with all possible
+ target ports(all FC and FCoE port for access_group.init_type == WWPN,
+ and the same to iSCSI)
+ """
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)
+
+ cim_init_mg = self._cim_init_mg_of_id(access_group.id,
+ raise_error=True)
+
+ cim_inits = self._cim_init_of_init_mg(cim_init_mg.path)
+ if len(cim_inits) == 0:
+ raise LsmError(ErrorNumber.EMPTY_ACCESS_GROUP,
+ "Access group %s is empty(no member), " %
+ access_group.id +
+ "will not do volume_mask()")
+
+ if access_group.init_type != AccessGroup.INIT_TYPE_WWPN and \
+ access_group.init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "SMI-S plugin only support iSCSI and FC/FCoE "
+ "access group volume masking, but got "
+ "access group init_type: %d" %
+ access_group.init_type)
+
+ cim_vol = self._get_cim_instance_by_id(
+ 'Volume', volume.id, ['Name'],
+ raise_error=True)
+
+ cim_gmm_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_GroupMaskingMappingService')
+
+ cim_spcs_path = self._c.AssociatorNames(
+ cim_init_mg.path,
+ AssocClass='CIM_AssociatedInitiatorMaskingGroup',
+ ResultClass='CIM_SCSIProtocolController')
+
+ if len(cim_spcs_path) == 0:
+ # We have to create the SPC and dev_mg now.
+ cim_tgt_mg_path = self._cim_tgt_mg_path_create(
+ cim_sys.path, cim_gmm_path, access_group.name,
+ access_group.init_type)
+ cim_dev_mg_path = self._cim_dev_mg_path_create(
+ cim_gmm_path, access_group.name, cim_vol.path, volume.id)
+ # Done when SPC created.
+ self._cim_spc_path_create(
+ cim_gmm_path, cim_init_mg.path, cim_tgt_mg_path,
+ cim_dev_mg_path, access_group.name)
+ else:
+ # CIM_InitiatorMaskingGroup might have multiple SPC when having
+ # many tgt_mg. It's seldom use, but possible.
+ for cim_spc_path in cim_spcs_path:
+ # Check whether already masked
+ cim_vol_pros = self._property_list_of_id('Volume')
+ cim_vols = self._c.Associators(
+ cim_spc_path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_StorageVolume',
+ PropertyList=cim_vol_pros)
+ for cur_cim_vol in cim_vols:
+ if self._vol_id(cur_cim_vol) == volume.id:
+ # Masked.
+ return None
+
+ # spc one-one map to dev_mg is mandatory in 1.5r6
+ cim_dev_mg_path = self._c.AssociatorNames(
+ cim_spc_path,
+ AssocClass='CIM_AssociatedDeviceMaskingGroup',
+ ResultClass='CIM_DeviceMaskingGroup')[0]
+ in_params = {
+ 'MaskingGroup': cim_dev_mg_path,
+ 'Members': [cim_vol.path],
+ }
+ (rc, out) = self._c.InvokeMethod(
+ 'AddMembers',
+ cim_gmm_path, **in_params)
+ self._wait_invoke(rc, out)
+ return None
+
@handle_cim_errors
def volume_mask(self, access_group, volume, flags=0):
"""
Grant access to a volume to an group
"""
- cim_ccs = self._get_class_instance(
- 'CIM_ControllerConfigurationService',
- 'SystemName', access_group.system_id)
- lun = self._get_cim_instance_by_id('Volume', volume.id, ['Name'])
- spc = self._get_cim_instance_by_id('AccessGroup', access_group.id)
+ mask_type = self._mask_type(raise_error=True)
+ # Workaround for EMC VNX/CX
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', volume.system_id, raise_error=True)
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ mask_type = Smis.MASK_TYPE_MASK

- if not lun:
- raise LsmError(ErrorNumber.NOT_FOUND_VOLUME, "Volume not present")
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ return self._volume_mask_group(access_group, volume, flags)
+ return self._volume_mask_old(access_group, volume, flags)

- if not spc:
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not present")
+ def _volume_mask_old(self, access_group, volume, flags):
+
+ cim_spc = self._cim_spc_of_id(access_group.id, raise_error=True)
+
+ cim_inits = self._cim_init_of_spc(cim_spc.path)
+ if len(cim_inits) == 0:
+ raise LsmError(ErrorNumber.EMPTY_ACCESS_GROUP,
+ "Access group %s is empty(no member), " %
+ access_group.id +
+ "will not do volume_mask()")
+
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', volume.system_id, raise_error=True)
+ cim_css_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_ControllerConfigurationService')
+
+ cim_vol = self._get_cim_instance_by_id(
+ 'Volume', volume.id, ['Name'], raise_error=True)

da = Smis.EXPOSE_PATHS_DA_READ_WRITE

- in_params = {'LUNames': [lun['Name']],
- 'ProtocolControllers': [spc.path],
+ in_params = {'LUNames': [cim_vol['Name']],
+ 'ProtocolControllers': [cim_spc.path],
'DeviceAccesses': [pywbem.Uint16(da)]}

- # Returns None or job id
- return self._pi("access_grant", Smis.JOB_RETRIEVE_NONE,
- *(self._c.InvokeMethod('ExposePaths', cim_ccs.path,
- **in_params)))[0]
+ (rc, out) = self._c.InvokeMethod(
+ 'ExposePaths',
+ cim_css_path, **in_params)
+ self._wait_invoke(rc, out)
+ return None

- def _wait(self, job):
+ def _volume_unmask_group(self, access_group, volume):
+ """
+ Use CIM_GroupMaskingMappingService.RemoveMembers() against
+ CIM_DeviceMaskingGroup
+ If SupportedDeviceGroupFeatures does not allow empty
+ DeviceMaskingGroup in SPC, we remove SPC and DeviceMaskingGroup.
+ """
+ cim_vol = self._get_cim_instance_by_id(
+ 'Volume', volume.id, raise_error=True)
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', volume.system_id, raise_error=True)

- status = self.job_status(job)[0]
+ cim_gmm_cap = self._c.Associators(
+ cim_sys.path,
+ AssocClass='CIM_ElementCapabilities',
+ ResultClass='CIM_GroupMaskingMappingCapabilities',
+ PropertyList=['SupportedDeviceGroupFeatures'])[0]

- while JobStatus.COMPLETE != status:
- time.sleep(0.5)
- status = self.job_status(job)[0]
+ flag_empty_dev_in_spc = False

- if JobStatus.COMPLETE != status:
- raise LsmError(ErrorNumber.PLUGIN_ERROR,
- "Expected no errors %s %s" % (job, str(status)))
+ if DMTF.GMM_CAP_DEV_MG_ALLOW_EMPTY_W_SPC in \
+ cim_gmm_cap['SupportedDeviceGroupFeatures']:
+ flag_empty_dev_in_spc = True
+
+ cim_gmm_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_GroupMaskingMappingService')
+
+ cim_spcs_path = self._c.AssociatorNames(
+ cim_vol.path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_SCSIProtocolController')
+ if len(cim_spcs_path) == 0:
+ # Already unmasked
+ return None
+
+ flag_init_mg_found = False
+ cur_cim_init_mg = None
+ # Seaching for CIM_DeviceMaskingGroup
+ for cim_spc_path in cim_spcs_path:
+ cim_init_mgs = self._c.Associators(
+ cim_spc_path,
+ AssocClass='CIM_AssociatedInitiatorMaskingGroup',
+ ResultClass='CIM_InitiatorMaskingGroup',
+ PropertyList=['InstanceID'])
+ for cim_init_mg in cim_init_mgs:
+ if md5(cim_init_mg['InstanceID']) == access_group.id:
+ flag_init_mg_found = True
+ break
+
+ if flag_init_mg_found:
+ cim_dev_mgs_path = self._c.AssociatorNames(
+ cim_spc_path,
+ AssocClass='CIM_AssociatedDeviceMaskingGroup',
+ ResultClass='CIM_DeviceMaskingGroup')
+
+ for cim_dev_mg_path in cim_dev_mgs_path:
+ if flag_empty_dev_in_spc is False:
+ # We have to check whether this volume is the last
+ # one in the DeviceMaskingGroup, if so, we have to
+ # delete the SPC
+ cur_cim_vols_path = self._c.AssociatorNames(
+ cim_dev_mg_path,
+ AssocClass='CIM_OrderedMemberOfCollection',
+ ResultClass='CIM_StorageVolume')
+ if len(cur_cim_vols_path) == 1:
+ # Now, delete SPC
+ in_params = {
+ 'ProtocolController': cim_spc_path,
+ }
+ (rc, out) = self._c.InvokeMethod(
+ 'DeleteMaskingView',
+ cim_gmm_path, **in_params)
+ self._wait_invoke(rc, out)
+
+ in_params={
+ 'MaskingGroup': cim_dev_mg_path,
+ 'Members': [cim_vol.path],
+ }
+ (rc, out) = self._c.InvokeMethod(
+ 'RemoveMembers',
+ cim_gmm_path, **in_params)
+ self._wait_invoke(rc, out)
+
+ return None

@handle_cim_errors
def volume_unmask(self, access_group, volume, flags=0):
- cim_ccs = self._get_class_instance(
- 'CIM_ControllerConfigurationService',
- 'SystemName', access_group.system_id)
- lun = self._get_cim_instance_by_id('Volume', volume.id, ['Name'])
- spc = self._get_cim_instance_by_id('AccessGroup', access_group.id)
+ mask_type = self._mask_type(raise_error=True)
+ # Workaround for EMC VNX/CX
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', volume.system_id, raise_error=True)
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ mask_type = Smis.MASK_TYPE_MASK

- if not lun:
- raise LsmError(ErrorNumber.NOT_FOUND_VOLUME, "Volume not present")
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ return self._volume_unmask_group(access_group, volume)
+ return self._volume_unmask_old(access_group, volume)

- if not spc:
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not present")
+ def _volume_unmask_old(self, access_group, volume):
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)
+ cim_ccs_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_ControllerConfigurationService')
+
+ cim_vol = self._get_cim_instance_by_id(
+ 'Volume', volume.id,
+ property_list=['Name'], raise_error=True)
+
+ cim_spc = self._cim_spc_of_id(access_group.id, raise_error=True)

- hide_params = {'LUNames': [lun['Name']],
- 'ProtocolControllers': [spc.path]}
- return self._pi("HidePaths", Smis.JOB_RETRIEVE_NONE,
- *(self._c.InvokeMethod('HidePaths', cim_ccs.path,
- **hide_params)))[0]
+ hide_params = {'LUNames': [cim_vol['Name']],
+ 'ProtocolControllers': [cim_spc.path]}

- def _is_access_group(self, cim_ag):
+ (rc, out) = self._c.InvokeMethod('HidePaths', cim_ccs_path,
+ **hide_params)
+ self._wait_invoke(rc, out)
+
+ return None
+
+ def _is_access_group(self, cim_spc):
rc = True
_SMIS_EMC_ADAPTER_ROLE_MASKING = 'MASK_VIEW'

- if 'EMCAdapterRole' in cim_ag:
+ if 'EMCAdapterRole' in cim_spc:
# Currently SNIA does not define LUN mapping.
# EMC is using their specific way for LUN mapping which
# expose their frontend ports as a SPC(SCSIProtocolController).
# which we shall filter out.
- emc_adp_roles = cim_ag['EMCAdapterRole'].split(' ')
+ emc_adp_roles = cim_spc['EMCAdapterRole'].split(' ')
if _SMIS_EMC_ADAPTER_ROLE_MASKING not in emc_adp_roles:
rc = False
return rc

- def _cim_ags_of(self, cim_sys, property_list=None):
+ def _cim_spc_of_id(self, access_group_id, property_list=None,
+ raise_error=False):
+ """
+ Return CIMInstance of CIM_SCSIProtocolController
+ Return None if not found.
+ Raise error if not found and raise_error is True
+ """
+ if property_list is None:
+ property_list = ['DeviceID']
+ else:
+ property_list = _merge_list(property_list, ['DeviceID'])
+
+ cim_spcs = self._enumerate('CIM_SCSIProtocolController', property_list)
+
+ for cim_spc in cim_spcs:
+ if md5(cim_spc['DeviceID']) == access_group_id:
+ return cim_spc
+
+ if raise_error is True:
+ raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
+ "AccessGroup %s not found" % access_group_id)
+ return None
+
+
+ def _cim_spc_of(self, cim_sys_path, property_list=None):
"""
Return a list of CIM_SCSIProtocolController.
Following SNIA SMIS 'Masking and Mapping Profile':
@@ -2025,14 +2403,14 @@ class Smis(IStorageAreaNetwork):
CIM_SCSIProtocolController
"""
cim_ccss_path = []
- rc_cim_ags = []
+ rc_cim_spcs = []

if property_list is None:
property_list = []

try:
cim_ccss_path = self._c.AssociatorNames(
- cim_sys.path,
+ cim_sys_path,
AssocClass='CIM_HostedService',
ResultClass='CIM_ControllerConfigurationService')
except CIMError as ce:
@@ -2052,18 +2430,18 @@ class Smis(IStorageAreaNetwork):
raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
"Got %d instance of " % len(cim_ccss_path) +
"ControllerConfigurationService from %s" %
- cim_sys.path + " in _cim_ags_of()")
- cim_ags = self._c.Associators(
+ cim_sys.path + " in _cim_spc_of()")
+ cim_spcs = self._c.Associators(
cim_ccs_path,
AssocClass='CIM_ConcreteDependency',
ResultClass='CIM_SCSIProtocolController',
PropertyList=property_list)
- for cim_ag in cim_ags:
- if self._is_access_group(cim_ag):
- rc_cim_ags.append(cim_ag)
- return rc_cim_ags
+ for cim_spc in cim_spcs:
+ if self._is_access_group(cim_spc):
+ rc_cim_spcs.append(cim_spc)
+ return rc_cim_spcs

- def _cim_init_of(self, cim_ag, property_list=None):
+ def _cim_init_of_spc(self, cim_spc_path, property_list=None):
"""
Take CIM_SCSIProtocolController and return a list of
CIM_StorageHardwareID, both are CIMInstance.
@@ -2102,13 +2480,13 @@ class Smis(IStorageAreaNetwork):
strict=False,
raise_error=False)):
return self._c.Associators(
- cim_ag.path,
+ cim_spc_path,
AssocClass='CIM_AssociatedPrivilege',
ResultClass='CIM_StorageHardwareID',
PropertyList=property_list)
else:
cim_aps_path = self._c.AssociatorNames(
- cim_ag.path,
+ cim_spc_path,
AssocClass='CIM_AuthorizedTarget',
ResultClass='CIM_AuthorizedPrivilege')
for cim_ap_path in cim_aps_path:
@@ -2119,144 +2497,478 @@ class Smis(IStorageAreaNetwork):
PropertyList=property_list))
return cim_inits

+ def _cim_init_of_init_mg(self, cim_init_mg_path, property_list=None):
+ """
+ Use this association:
+ CIM_InitiatorMaskingGroup
+ |
+ | CIM_MemberOfCollection
+ v
+ CIM_StorageHardwareID
+ """
+ if property_list is None:
+ property_list = []
+ return self._c.Associators(
+ cim_init_mg_path,
+ AssocClass='CIM_MemberOfCollection',
+ ResultClass='CIM_StorageHardwareID',
+ PropertyList=property_list)
+
@handle_cim_errors
def volumes_accessible_by_access_group(self, access_group, flags=0):
- g = self._get_class_instance('CIM_SCSIProtocolController', 'DeviceID',
- access_group.id)
- if g:
- logical_units = self._c.Associators(
- g.path, AssocClass='CIM_ProtocolControllerForUnit')
- return [self._new_vol(v) for v in logical_units]
+ mask_type = self._mask_type(raise_error=True)
+ cim_vols = []
+ cim_vol_pros = self._cim_vol_pros()
+
+ # Workaround for EMC VNX/CX
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ mask_type = Smis.MASK_TYPE_MASK
+
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_init_mg = self._cim_init_mg_of_id(
+ access_group.id, raise_error=True)
+
+ cim_spcs_path = self._c.AssociatorNames(
+ cim_init_mg.path,
+ AssocClass='CIM_AssociatedInitiatorMaskingGroup',
+ ResultClass='CIM_SCSIProtocolController')
+
+ for cim_spc_path in cim_spcs_path:
+ cim_vols.extend(
+ self._c.Associators(
+ cim_spc_path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_StorageVolume',
+ PropertyList=cim_vol_pros))
else:
- raise LsmError(
- ErrorNumber.PLUGIN_ERROR,
- 'Error: access group %s does not exist!' % access_group.id)
+ cim_spc = self._cim_spc_of_id(access_group.id, raise_error=True)
+ cim_vols = self._c.Associators(
+ cim_spc.path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_StorageVolume',
+ PropertyList=cim_vol_pros)
+
+ return [self._new_vol(v) for v in cim_vols]

@handle_cim_errors
def access_groups_granted_to_volume(self, volume, flags=0):
- vol = self._get_cim_instance_by_id('Volume', volume.id)
+ rc = []
+ mask_type = self._mask_type(raise_error=True)
+ cim_vol = self._get_cim_instance_by_id(
+ 'Volume', volume.id, raise_error=True)
+
+ # Workaround for EMC VNX/CX
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', volume.id, raise_error=True)
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ mask_type = Smis.MASK_TYPE_MASK
+
+ cim_spc_pros = None
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_spc_pros = []
+ else:
+ cim_spc_pros = self._cim_spc_pros()

- if vol:
- cim_ags = self._c.Associators(
- vol.path,
- AssocClass='CIM_ProtocolControllerForUnit',
- ResultClass='CIM_SCSIProtocolController')
+ cim_spcs = self._c.Associators(
+ cim_vol.path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_SCSIProtocolController',
+ PropertyList=cim_spc_pros)
+
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_init_mg_pros = self._cim_init_mg_pros()
+ for cim_spc in cim_spcs:
+ cim_init_mgs = self._c.Associators(
+ cim_spc.path,
+ AssocClass='CIM_AssociatedInitiatorMaskingGroup',
+ ResultClass='CIM_InitiatorMaskingGroup',
+ PropertyList=cim_init_mg_pros)
+ rc.extend(
+ list(self._cim_init_mg_to_lsm(x, volume.system_id)
+ for x in cim_init_mgs))
+ else:
+ for cim_spc in cim_spcs:
+ if self._is_access_group(cim_spc):
+ rc.extend(
+ [self._cim_spc_to_lsm(cim_spc, volume.system_id)])

- access_groups = []
- for cim_ag in cim_ags:
- if self._is_access_group(cim_ag):
- access_groups.extend([self._cim_ag_to_lsm(cim_ag)])
+ return rc

- return access_groups
+ def _cim_init_mg_of_id(self, access_group_id, property_list=None,
+ raise_error=False):
+ """
+ Return CIMInstance of CIM_InitiatorMaskingGroup
+ Return None if not found.
+ Raise error if not found and raise_error is True
+ """
+ if property_list is None:
+ property_list = ['InstanceID']
else:
- raise LsmError(
- ErrorNumber.PLUGIN_ERROR,
- 'Error: access group %s does not exist!' % volume.id)
+ property_list = _merge_list(property_list, ['InstanceID'])
+
+ cim_init_mgs = self._enumerate(
+ 'CIM_InitiatorMaskingGroup', property_list)
+
+ for cim_init_mg in cim_init_mgs:
+ if md5(cim_init_mg['InstanceID']) == access_group_id:
+ return cim_init_mg
+
+ if raise_error is True:
+ raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
+ "AccessGroup %s not found" % access_group_id)
+ return None
+
+
+ def _cim_init_mg_of(self, cim_sys_path, property_list=None):
+ """
+ There is no CIM_ComputerSystem association to
+ CIM_InitiatorMaskingGroup, we use this association:
+ CIM_ComputerSystem
+ |
+ | CIM_HostedService
+ v
+ CIM_GroupMaskingMappingService
+ |
+ | CIM_ServiceAffectsElement
+ v
+ CIM_InitiatorMaskingGroup
+ """
+ if property_list is None:
+ property_list = []
+
+ cim_gmm_path = self._get_cim_service_path(
+ cim_sys_path, 'CIM_GroupMaskingMappingService')
+
+ return self._c.Associators(
+ cim_gmm_path,
+ AssocClass='CIM_ServiceAffectsElement',
+ ResultClass='CIM_InitiatorMaskingGroup',
+ PropertyList=property_list)
+
+ def _mask_type(self, raise_error=False):
+ """
+ Return Smis.MASK_TYPE_NO_SUPPORT, MASK_TYPE_MASK or MASK_TYPE_GROUP
+ For fallback_mode, return MASK_TYPE_MASK
+ if 'Group Masking and Mapping' profile is supported, return
+ MASK_TYPE_GROUP
+
+ If raise_error == False, just return Smis.MASK_TYPE_NO_SUPPORT
+ or, raise NO_SUPPORT error.
+ """
+ if self.fallback_mode:
+ return Smis.MASK_TYPE_MASK
+ if self._profile_is_supported(SNIA.GROUP_MASK_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_5,
+ strict=False,
+ raise_error=False):
+ return Smis.MASK_TYPE_GROUP
+ if self._profile_is_supported(SNIA.MASK_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False):
+ return Smis.MASK_TYPE_MASK
+ if raise_error:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "Target SMI-S provider does not support "
+ "%s version %s or %s version %s" %
+ (SNIA.MASK_PROFILE, SNIA.SMIS_SPEC_VER_1_4,
+ SNIA.GROUP_MASK_PROFILE, SNIA.SMIS_SPEC_VER_1_5))
+ return Smis.MASK_TYPE_NO_SUPPORT

@handle_cim_errors
def access_groups(self, search_key=None, search_value=None, flags=0):
- if not self.fallback_mode:
- self._profile_is_supported(SNIA.MASK_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False,
- raise_error=True)
rc = []
- cim_ag_pros = self._cim_ag_pros()
+ mask_type = self._mask_type(raise_error=True)
+
cim_sys_pros = self._property_list_of_id('System')
cim_syss = self._root_cim_syss(cim_sys_pros)
+
+ cim_spc_pros = self._cim_spc_pros()
for cim_sys in cim_syss:
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ # Workaround for EMC VNX/CX.
+ # Even they claim support of Group M&M via
+ # CIM_RegisteredProfile, but actually they don't support it.
+ mask_type = Smis.MASK_TYPE_MASK
+
system_id = self._sys_id(cim_sys)
- cim_ags = self._cim_ags_of(cim_sys, cim_ag_pros)
- rc.extend(
- list(self._cim_ag_to_lsm(cim_ag, system_id)
- for cim_ag in cim_ags))
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ cim_init_mg_pros = self._cim_init_mg_pros()
+ cim_init_mgs = self._cim_init_mg_of(cim_sys.path,
+ cim_init_mg_pros)
+ rc.extend(
+ list(self._cim_init_mg_to_lsm(x, system_id)
+ for x in cim_init_mgs))
+ elif mask_type == Smis.MASK_TYPE_MASK:
+ cim_spcs = self._cim_spc_of(cim_sys.path, cim_spc_pros)
+ rc.extend(
+ list(self._cim_spc_to_lsm(cim_spc, system_id)
+ for cim_spc in cim_spcs))
+ else:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_get_cim_spc_by_id(): Got invalid mask_type: "
+ "%s" % mask_type)

return search_property(rc, search_key, search_value)

- def _initiator_create(self, cim_sys_path, init_id, dmtf_id_type):
+ def _cim_init_check_or_create(self, cim_sys_path, init_id, init_type):
+ """
+ Check whether CIM_StorageHardwareID exists, if not, create new one.
+ """
+ cim_init = self._get_cim_instance_by_id(
+ 'Initiator', init_id, raise_error=False, property_list=['IDType'])
+
+ # In rare chance, cim_init might holding different init_type
+ # As ExposePaths is using init_id, hence we raise error instead of
+ # creating new one
+ if cim_init:
+ if _dmtf_init_type_to_lsm(cim_init) != init_type:
+ raise LsmError(ErrorNumber.EXISTS_INITIATOR,
+ "Another initiator exists with the same ID "
+ "%s, but different init_type %d:" %
+ (init_id, _dmtf_init_type_to_lsm(cim_init)))
+ return cim_init
+
+ # Create new one
+ dmtf_id_type = _lsm_init_type_to_dmtf(init_type)
+ if dmtf_id_type is None:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "SMI-S Plugin does not support init_type %d" %
+ init_type)
+
+ cim_init = self._cim_init_create(
+ cim_sys_path, init_id, dmtf_id_type)
+
+ return cim_init
+
+
+ def _cim_init_create(self, cim_sys_path, init_id, dmtf_id_type):
"""
Create a CIM_StorageHardwareID.
Raise error if failed. Return if pass.
"""
- cim_hw_srvs = self._c.AssociatorNames(
- cim_sys_path,
- ResultClass='CIM_StorageHardwareIDManagementService',
- AssocClass='CIM_HostedService')
- if len(cim_hw_srvs) == 0:
- raise LsmError(ErrorNumber.NO_SUPPORT,
- "Target SMI-S provider does not support "
- "access_group_initiator_add(): No "
- "CIM_StorageHardwareIDManagementService to create"
- "new initiator")
- if len(cim_hw_srvs) != 1:
- raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
- "_initiator_create(): Got more than one "
- "CIM_StorageHardwareIDManagementService")
+ cim_hw_srv_path = self._get_cim_service_path(
+ cim_sys_path, 'CIM_StorageHardwareIDManagementService')

in_params = {'StorageID': init_id,
'IDType': pywbem.Uint16(dmtf_id_type)}

(rc, out) = self._c.InvokeMethod('CreateStorageHardwareID',
- cim_hw_srvs[0], **in_params)
- if not rc:
- return
+ cim_hw_srv_path, **in_params)
+ # CreateStorageHardwareID does not allow ASYNC
+ return self._wait_invoke(
+ rc, out, out_key='HardwareID',
+ expect_class='CIM_StorageHardwareID')

- # Ideally, we should handle CIM Error here in stead of raise
- # LSM_PLUGIN_BUG error. Let's wait user report on bug.
- raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
- 'Error on _initiator_create(): rc: "%s", out: "%s"'
- % (str(rc), out))
+ def _ag_init_add_group(self, access_group, init_id, init_type):
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)
+
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "EMC VNX/CX require WWNN defined when adding a "
+ "new initiator which is not supported by LSM yet. "
+ "Please do it via EMC vendor specific tools.")
+
+ cim_init_mg = self._cim_init_mg_of_id(access_group.id)
+
+ cim_init_pros = self._property_list_of_id('Initiator')
+ cim_init_pros.extend(['IDType'])
+ exist_cim_inits = self._cim_init_of_init_mg(
+ cim_init_mg.path, cim_init_pros)
+
+ # Check whether already added.
+ for exist_cim_init in exist_cim_inits:
+ if self._init_id(exist_cim_init) == init_id and \
+ _dmtf_init_type_to_lsm(exist_cim_init) == init_type:
+ return copy.deepcopy(access_group)
+
+ cim_init = self._cim_init_check_or_create(
+ cim_sys.path, init_id, init_type)
+
+ cim_gmm_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_GroupMaskingMappingService')
+
+ in_params = {
+ 'MaskingGroup': cim_init_mg.path,
+ 'Members': [cim_init.path],
+ }
+ (rc, out) = self._c.InvokeMethod('AddMembers',
+ cim_gmm_path, **in_params)
+
+ new_cim_init_mg_path = self._wait_invoke(
+ rc, out, out_key='MaskingGroup',
+ expect_class='CIM_InitiatorMaskingGroup')
+ cim_init_mg_pros = self._cim_init_mg_pros()
+ new_cim_init_mg = self._c.GetInstance(
+ new_cim_init_mg_path, PropertyList=cim_init_mg_pros, LocalOnly=False)
+ return self._cim_init_mg_to_lsm(
+ new_cim_init_mg, access_group.system_id)

@handle_cim_errors
def access_group_initiator_add(self, access_group, init_id, init_type,
flags=0):
+ mask_type = self._mask_type(raise_error=True)
+
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ return self._ag_init_add_group(access_group, init_id, init_type)
+ else:
+ return self._ag_init_add_old(access_group, init_id, init_type)
+
+ def _ag_init_add_old(self, access_group, init_id, init_type):
# CIM_StorageHardwareIDManagementService.CreateStorageHardwareID()
# is mandatory since 1.4rev6
- if not self.fallback_mode:
- self._profile_is_supported(SNIA.MASK_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False,
- raise_error=True)
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)

- cim_sys = self._get_cim_instance_by_id('System',
- access_group.system_id)
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "EMC VNX/CX require WWNN defined when adding "
+ "new initiator which is not supported by LSM yet. "
+ "Please do it via EMC vendor specific tools.")

- # Check to see if we have this initiator already, if we don't create
- # it and then add to the view.
- if self._get_cim_instance_by_id(
- 'Initiator', init_id, raise_error=False) is None:
- dmtf_id_type = _lsm_init_type_to_dmtf(init_type)
- if dmtf_id_type is None:
- raise LsmError(ErrorNumber.NO_SUPPORT,
- "SMI-S Plugin does not support init_type %d"
- % init_type)
- self._initiator_create(cim_sys.path, init_id, dmtf_id_type)
+ cim_spc = self._cim_spc_of_id(access_group.id, raise_error=True)
+
+ cim_init_pros = self._property_list_of_id('Initiator')
+ cim_init_pros.extend(['IDType'])
+ exist_cim_inits = self._cim_init_of_spc(cim_spc.path, cim_init_pros)
+
+ for exist_cim_init in exist_cim_inits:
+ if self._init_id(exist_cim_init) == init_id:
+ if _dmtf_init_type_to_lsm(exist_cim_init) != init_type:
+ raise LsmError(ErrorNumber.EXISTS_INITIATOR,
+ "Another initiator exists with the same ID "
+ "%s, but different init_type %d:" %
+ (init_id,
+ _dmtf_init_type_to_lsm(exist_cim_init)))

- cim_ag = self._get_cim_instance_by_id('AccessGroup', access_group.id)
- cim_ccs = self._get_class_instance(
- 'CIM_ControllerConfigurationService',
- 'SystemName', access_group.system_id)
+ return copy.deepcopy(access_group)
+
+ # Check to see if we have this initiator already, if not we
+ # create it and then add to the view.
+
+ self._cim_init_check_or_create(cim_sys.path, init_id, init_type)
+
+ cim_ccs_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_ControllerConfigurationService')

in_params = {'InitiatorPortIDs': [init_id],
- 'ProtocolControllers': [cim_ag.path]}
+ 'ProtocolControllers': [cim_spc.path]}

- # Returns None or job id
- return self._pi("access_group_initiator_add", Smis.JOB_RETRIEVE_NONE,
- *(self._c.InvokeMethod('ExposePaths', cim_ccs.path,
- **in_params)))[0]
+ (rc, out) = self._c.InvokeMethod('ExposePaths',
+ cim_ccs_path, **in_params)
+ cim_spc_path = self._wait_invoke(
+ rc, out, out_key='ProtocolControllers', flag_out_array=True,
+ expect_class='CIM_SCSIProtocolController')
+
+ cim_spc_pros = self._cim_spc_pros()
+ cim_spc = self._c.GetInstance(
+ cim_spc_path, PropertyList=cim_spc_pros, LocalOnly=False)
+ return self._cim_spc_to_lsm(cim_spc, access_group.system_id)
+
+
+ def _ag_init_del_group(self, access_group, init_id):
+ """
+ LSM Require support of empty access group.
+ So GMM_CAP_INIT_MG_ALLOW_EMPTY should be support by target SMI-S
+ provider. To make thing simple, even current access_group has 2+
+ init_id, we still raise NO_SUPPORT error if empty access group not
+ supported.
+ """
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)
+
+ cim_gmm_cap = self._c.Associators(
+ cim_sys.path,
+ AssocClass='CIM_ElementCapabilities',
+ ResultClass='CIM_GroupMaskingMappingCapabilities',
+ PropertyList=['SupportedInitiatorGroupFeatures'])[0]
+ if DMTF.GMM_CAP_INIT_MG_ALLOW_EMPTY not in \
+ cim_gmm_cap['SupportedInitiatorGroupFeatures']:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "Target SMI-S provider does not support empty "
+ "CIM_InitiatorMaskingGroup which is required by "
+ "LSM for access_group_initiator_delete()")
+
+ cim_init_mg_pros = self._cim_init_mg_pros()
+ cim_init_mg = self._cim_init_mg_of_id(
+ access_group.id, raise_error=True, property_list=cim_init_mg_pros)
+
+ cim_init_pros = self._property_list_of_id('Initiator')
+ cur_cim_inits = self._cim_init_of_init_mg(
+ cim_init_mg.path, property_list=cim_init_pros)
+
+ cim_init = None
+ for cur_cim_init in cur_cim_inits:
+ if self._init_id(cur_cim_init) == init_id:
+ cim_init = cur_cim_init
+ break
+
+ if cim_init is None:
+ return self._cim_init_mg_to_lsm(
+ cim_init_mg, access_group.system_id)
+
+ cim_gmm_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_GroupMaskingMappingService')
+
+ if len(cur_cim_inits) == 1:
+ # Check whether we have any volume masked.
+ cim_spcs_path = self._c.AssociatorNames(
+ cim_init_mg.path,
+ AssocClass='CIM_AssociatedInitiatorMaskingGroup',
+ ResultClass='CIM_SCSIProtocolController')
+ for cim_spc_path in cim_spcs_path:
+ if len(self._c.AssociatorNames(
+ cim_spc_path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_StorageVolume')) >= 1:
+ raise LsmError(ErrorNumber.ACCESS_GROUP_BUSY,
+ "Refuse to remove last initiator member "
+ "from access group which have volume "
+ "masked to")
+
+ # RemoveMembers from InitiatorMaskingGroup
+ in_params = {
+ 'MaskingGroup': cim_init_mg.path,
+ 'Members': [cim_init.path],
+ }
+
+ (rc, out) = self._c.InvokeMethod(
+ 'RemoveMembers',
+ cim_gmm_path, **in_params)
+ self._wait_invoke(rc, out)
+ return self._cim_init_mg_to_lsm(
+ cim_init_mg, access_group.system_id)

@handle_cim_errors
def access_group_initiator_delete(self, access_group, init_id, flags=0):
- cim_ag = self._get_cim_instance_by_id('AccessGroup', access_group.id)
- cim_ccs = self._get_class_instance(
- 'CIM_ControllerConfigurationService',
- 'SystemName', access_group.system_id)
+ mask_type = self._mask_type(raise_error=True)
+
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ return self._ag_init_del_group(access_group, init_id)
+ else:
+ return self._ag_init_del_old(access_group, init_id)
+
+ def _ag_init_del_old(self, access_group, init_id):
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', access_group.system_id, raise_error=True)
+
+ cim_spc = self._cim_spc_of_id(access_group.id, raise_error=True)
+
+ cim_ccs_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_ControllerConfigurationService')

hide_params = {'InitiatorPortIDs': [init_id],
- 'ProtocolControllers': [cim_ag.path]}
- return self._pi("HidePaths", Smis.JOB_RETRIEVE_NONE,
- *(self._c.InvokeMethod('HidePaths', cim_ccs.path,
- **hide_params)))[0]
+ 'ProtocolControllers': [cim_spc.path]}
+ (rc, out) = self._c.InvokeMethod(
+ 'HidePaths', cim_ccs.path, **hide_params)
+
+ self._wait_invoke(rc, out)
+ return None

@handle_cim_errors
def job_free(self, job_id, flags=0):
@@ -3360,10 +4072,10 @@ class Smis(IStorageAreaNetwork):
# but we would like to take that risk instead of
# skipping basic support of old SMIS provider.
if e[0] == pywbem.CIM_ERR_INVALID_CLASS:
- cim_syss = self._c.EnumerateInstances(
- 'CIM_ComputerSystem',
- PropertyList=property_list,
- LocalOnly=False)
+ cim_syss = self._c.EnumerateInstances(
+ 'CIM_ComputerSystem',
+ PropertyList=property_list,
+ LocalOnly=False)
else:
raise

@@ -3409,14 +4121,110 @@ class Smis(IStorageAreaNetwork):
else:
return cim_syss

+ def _fc_tgt_is_supported(self):
+ """
+ Return True if FC Target Port 1.4+ profile is supported.
+ For fallback_mode, always return True.
+ Return False else.
+ """
+ if self.fallback_mode:
+ return True
+ flag_fc_support = self._profile_is_supported(
+ SNIA.FC_TGT_PORT_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False)
+ # One more check for NetApp Typo:
+ # NetApp: 'FC Target Port'
+ # SMI-S: 'FC Target Ports'
+ # Bug reported.
+ if not flag_fc_support:
+ flag_fc_support = self._profile_is_supported(
+ 'FC Target Port',
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False)
+ if flag_fc_support:
+ return True
+ else:
+ return False
+
+ def _iscsi_tgt_is_supported(self):
+ """
+ Return True if FC Target Port 1.4+ profile is supported.
+ For fallback_mode, always return True.
+ Return False else.
+ """
+ if self.fallback_mode:
+ return True
+ flag_iscsi_support = self._profile_is_supported(
+ SNIA.ISCSI_TGT_PORT_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False)
+ if flag_iscsi_support:
+ return True
+ else:
+ return False
+
+ def _multi_sys_is_supported(self):
+ """
+ Return True if Multiple ComputerSystem 1.4+ profile is supported.
+ For fallback_mode, always return True.
+ Return False else.
+ """
+ if self.fallback_mode:
+ return True
+ flag_multi_sys_support = self._profile_is_supported(
+ SNIA.MULTI_SYS_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False)
+ if flag_multi_sys_support:
+ return True
+ else:
+ return False
+
@staticmethod
- def _cim_fc_tgt_to_lsm(cim_fc_tgt):
+ def _is_frontend_fc_tgt(cim_fc_tgt):
+ """
+ Check CIM_FCPort['UsageRestriction'] for frontend port.
"""
- When provider support "Multiple Computer System" profile,
- CIM_FCPort['SystemName'] might not the name of root CIM_ComputerSystem
+ dmtf_usage = cim_fc_tgt['UsageRestriction']
+ if dmtf_usage == DMTF.TGT_PORT_USAGE_FRONTEND_ONLY or \
+ dmtf_usage == DMTF.TGT_PORT_USAGE_UNRESTRICTED:
+ return True
+ return False

- Caller should update cim_fc_tgt['SystemName'] with the name of
- root CIM_ComputerSystem
+ def _cim_fc_tgt_of(self, cim_sys_path, property_list=None):
+ """
+ Get all CIM_FCPort (frontend only) from CIM_ComputerSystem and its
+ leaf CIM_ComputerSystem
+ """
+ rc = []
+ if property_list is None:
+ property_list = ['UsageRestriction']
+ else:
+ property_list = _merge_list(property_list, ['UsageRestriction'])
+ all_cim_syss_path = [cim_sys_path]
+ if self._multi_sys_is_supported():
+ all_cim_syss_path.extend(
+ self._leaf_cim_syss_path_of(cim_sys_path))
+ for cur_cim_sys_path in all_cim_syss_path:
+ cur_cim_fc_tgts = self._c.Associators(
+ cur_cim_sys_path,
+ AssocClass='CIM_SystemDevice',
+ ResultClass='CIM_FCPort',
+ PropertyList=property_list)
+ for cim_fc_tgt in cur_cim_fc_tgts:
+ if Smis._is_frontend_fc_tgt(cim_fc_tgt):
+ rc.extend([cim_fc_tgt])
+ return rc
+
+ @staticmethod
+ def _cim_fc_tgt_to_lsm(cim_fc_tgt, system_id):
+ """
+ Convert CIM_FCPort to Lsm.TargetPort
"""
port_id = md5(cim_fc_tgt['DeviceID'])
port_type = _lsm_tgt_port_type_of_cim_fc_tgt(cim_fc_tgt)
@@ -3424,7 +4232,6 @@ class Smis(IStorageAreaNetwork):
# No need to check.
wwpn = _hex_string_format(cim_fc_tgt['PermanentAddress'], 16, 2)
port_name = cim_fc_tgt['ElementName']
- system_id = cim_fc_tgt['SystemName']
plugin_data = None
return TargetPort(port_id, port_type, wwpn, wwpn, wwpn, port_name,
system_id, plugin_data)
@@ -3468,6 +4275,31 @@ class Smis(IStorageAreaNetwork):
% (cim_iscsi_pg_path, cim_iscsi_nodes))
return cim_iscsi_nodes[0]['Name']

+ def _cim_iscsi_pg_of(self, cim_sys_path, property_list=None):
+ """
+ Get all CIM_iSCSIProtocolEndpoint(Target only) from CIM_ComputerSystem
+ and its leaf CIM_ComputerSystem
+ """
+ rc = []
+ if property_list is None:
+ property_list = ['Role']
+ else:
+ property_list = _merge_list(property_list, ['Role'])
+ all_cim_syss_path = [cim_sys_path]
+ if self._multi_sys_is_supported():
+ all_cim_syss_path.extend(
+ self._leaf_cim_syss_path_of(cim_sys_path))
+ for cur_cim_sys_path in all_cim_syss_path:
+ cur_cim_iscsi_pgs = self._c.Associators(
+ cur_cim_sys_path,
+ AssocClass='CIM_HostedAccessPoint',
+ ResultClass='CIM_iSCSIProtocolEndpoint',
+ PropertyList=property_list)
+ for cim_iscsi_pg in cur_cim_iscsi_pgs:
+ if cim_iscsi_pg['Role'] == DMTF.ISCSI_TGT_ROLE_TARGET:
+ rc.extend([cim_iscsi_pg])
+ return rc
+
def _cim_iscsi_pg_to_lsm(self, cim_iscsi_pg, system_id):
"""
Return a list of TargetPort CIM_iSCSIProtocolEndpoint
@@ -3603,157 +4435,377 @@ class Smis(IStorageAreaNetwork):
port_name, system_id, plugin_data)])
return rc

- def _leaf_cim_syss_of(self, cim_sys_path, property_list=None):
+ def _leaf_cim_syss_path_of(self, cim_sys_path):
"""
- Return a list of CIMInstance of CIM_ComputerSystem
+ Return a list of CIMInstanceName of leaf CIM_ComputerSystem
"""
- if property_list is None:
- property_list = []
-
max_loop_count = 10 # There is no storage array need 10 layer of
# Computer
loop_counter = max_loop_count
rc = []
- leaf_cim_syss = self._c.Associators(
- cim_sys_path,
- ResultClass='CIM_ComputerSystem',
- AssocClass='CIM_ComponentCS',
- Role='GroupComponent',
- ResultRole='PartComponent',
- PropertyList=property_list)
- if len(leaf_cim_syss) > 0:
- rc = leaf_cim_syss
- for cim_sys in leaf_cim_syss:
- rc.extend(self._leaf_cim_syss_of(cim_sys.path, property_list))
+ leaf_cim_syss_path = []
+ try:
+ leaf_cim_syss_path = self._c.AssociatorNames(
+ cim_sys_path,
+ ResultClass='CIM_ComputerSystem',
+ AssocClass='CIM_ComponentCS',
+ Role='GroupComponent',
+ ResultRole='PartComponent')
+ except CIMError as ce:
+ error_code = tuple(ce)[0]
+ if error_code == pywbem.CIM_ERR_INVALID_CLASS or \
+ error_code == pywbem.CIM_ERR_NOT_SUPPORTED:
+ return []
+
+ if len(leaf_cim_syss_path) > 0:
+ rc = leaf_cim_syss_path
+ for cim_sys_path in leaf_cim_syss_path:
+ rc.extend(self._leaf_cim_syss_path_of(cim_sys_path))

return rc

@handle_cim_errors
def target_ports(self, search_key=None, search_value=None, flags=0):
rc = []
- flag_fc_support = True # we should try both for fallback mode
- flag_iscsi_support = True
- flag_multi_sys_support = False
- if not self.fallback_mode:
- flag_fc_support = self._profile_is_supported(
- SNIA.FC_TGT_PORT_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False,
- raise_error=False)
- # One more check for NetApp Typo:
- # NetApp: 'FC Target Port'
- # SMI-S: 'FC Target Ports'
- # Bug reported.
- if not flag_fc_support:
- flag_fc_support = self._profile_is_supported(
- 'FC Target Port',
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False,
- raise_error=False)
-
- flag_iscsi_support = self._profile_is_supported(
- SNIA.ISCSI_TGT_PORT_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False,
- raise_error=False)
+ flag_fc_support = self._fc_tgt_is_supported()
+ flag_iscsi_support = self._iscsi_tgt_is_supported()

- if flag_fc_support is None and flag_iscsi_support is None:
- raise LsmError(ErrorNumber.NO_SUPPORT,
- "Target SMI-S provider does not support any of"
- "these profiles: '%s %s', '%s %s'"
- % (SNIA.SMIS_SPEC_VER_1_4,
- SNIA.FC_TGT_PORT_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- SNIA.ISCSI_TGT_PORT_PROFILE))
- if not self.fallback_mode:
- flag_multi_sys_support = self._profile_is_supported(
- SNIA.MULTI_SYS_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False,
- raise_error=False)
+ if flag_fc_support is False and flag_iscsi_support is False:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "Target SMI-S provider does not support any of"
+ "these profiles: '%s %s', '%s %s'"
+ % (SNIA.SMIS_SPEC_VER_1_4,
+ SNIA.FC_TGT_PORT_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ SNIA.ISCSI_TGT_PORT_PROFILE))

cim_fc_tgt_pros = ['UsageRestriction', 'ElementName', 'SystemName',
'PermanentAddress', 'PortDiscriminator',
'LinkTechnology', 'DeviceID']

- if flag_fc_support:
- cim_fc_tgts = []
- if flag_multi_sys_support:
+ cim_syss = self._root_cim_syss(
+ property_list=self._property_list_of_id('System'))
+ for cim_sys in cim_syss:
+ system_id = self._sys_id(cim_sys)
+ if flag_fc_support:
# CIM_FCPort might be not belong to root cim_sys
# In that case, CIM_FCPort['SystemName'] will not be
- # the name of root CIM_ComputerSystem
- cim_syss = self._root_cim_syss(
- property_list=self._property_list_of_id('System'))
- for cim_sys in cim_syss:
- cim_fc_tgts.extend(
- self._c.Associators(
- cim_sys.path,
- AssocClass='CIM_SystemDevice',
- ResultClass='CIM_FCPort',
- PropertyList=cim_fc_tgt_pros))
-
- system_id = self._sys_id(cim_sys)
- leaf_cim_syss = self._leaf_cim_syss_of(cim_sys.path)
- for leaf_cim_sys in leaf_cim_syss:
- cur_cim_fc_tgts = self._c.Associators(
- leaf_cim_sys.path,
- AssocClass='CIM_SystemDevice',
- ResultClass='CIM_FCPort',
- PropertyList=cim_fc_tgt_pros)
-
- # Update SystemName which will be used as system_id
- for cim_fc_tgt in cur_cim_fc_tgts:
- cim_fc_tgt['SystemName'] = system_id
- cim_fc_tgts.extend([cim_fc_tgt])
- else:
- cim_fc_tgts = self._enumerate('CIM_FCPort',
- property_list=cim_fc_tgt_pros)
- for cim_fc_tgt in cim_fc_tgts:
- dmtf_usage = cim_fc_tgt['UsageRestriction']
- if dmtf_usage != DMTF.TGT_PORT_USAGE_FRONTEND_ONLY and \
- dmtf_usage != DMTF.TGT_PORT_USAGE_UNRESTRICTED:
- continue
- rc.extend([Smis._cim_fc_tgt_to_lsm(cim_fc_tgt)])
-
- if flag_iscsi_support:
- # As we need do more Associators() call in _cim_iscsi_pg_to_lsm()
- # We can not change SystemName as we did for FC/FCoE
- cim_iscsi_pg_pros = ['Role']
-
- cim_syss = self._root_cim_syss(
- property_list=self._property_list_of_id('System'))
- for cim_sys in cim_syss:
- cim_iscsi_pgs = self._c.Associators(
- cim_sys.path,
- AssocClass='CIM_HostedAccessPoint',
- ResultClass='CIM_iSCSIProtocolEndpoint',
- PropertyList=cim_iscsi_pg_pros)
- system_id = self._sys_id(cim_sys)
- if flag_multi_sys_support:
- leaf_cim_syss = self._leaf_cim_syss_of(cim_sys.path)
- for leaf_cim_sys in leaf_cim_syss:
- cim_iscsi_pgs.extend(self._c.Associators(
- leaf_cim_sys.path,
- AssocClass='CIM_HostedAccessPoint',
- ResultClass='CIM_iSCSIProtocolEndpoint',
- PropertyList=cim_iscsi_pg_pros))
+ # the name of root CIM_ComputerSystem.
+ cim_fc_tgt_pros = ['UsageRestriction', 'ElementName',
+ 'SystemName', 'PermanentAddress',
+ 'PortDiscriminator', 'LinkTechnology',
+ 'DeviceID']
+ cim_fc_tgts = self._cim_fc_tgt_of(cim_sys.path,
+ cim_fc_tgt_pros)
+ rc.extend(
+ list(
+ Smis._cim_fc_tgt_to_lsm(x, system_id)
+ for x in cim_fc_tgts))
+
+ if flag_iscsi_support:
+ cim_iscsi_pgs = self._cim_iscsi_pg_of(cim_sys.path)
for cim_iscsi_pg in cim_iscsi_pgs:
- if cim_iscsi_pg['Role'] != DMTF.ISCSI_TGT_ROLE_TARGET:
- continue
rc.extend(
self._cim_iscsi_pg_to_lsm(cim_iscsi_pg, system_id))
- # NetApp is sharing CIM_TCPProtocolEndpoint which
- # cause duplicate TargetPort. It's a long story, they heard my
- # bug report.
- if len(cim_syss) >= 1 and \
- cim_syss[0].classname == 'ONTAP_StorageSystem':
- id_list = []
- new_rc = []
- # We keep the original list order by not using dict.values()
- for lsm_tp in rc:
- if lsm_tp.id not in id_list:
- id_list.extend([lsm_tp.id])
- new_rc.extend([lsm_tp])
- rc = new_rc
+
+ # NetApp is sharing CIM_TCPProtocolEndpoint which
+ # cause duplicate TargetPort. It's a long story, they heard my
+ # bug report.
+ if len(cim_syss) >= 1 and \
+ cim_syss[0].classname == 'ONTAP_StorageSystem':
+ id_list = []
+ new_rc = []
+ # We keep the original list order by not using dict.values()
+ for lsm_tp in rc:
+ if lsm_tp.id not in id_list:
+ id_list.extend([lsm_tp.id])
+ new_rc.extend([lsm_tp])
+ rc = new_rc

return search_property(rc, search_key, search_value)
+
+ def _cim_init_mg_pros(self):
+ return ['ElementName', 'InstanceID']
+
+ def _cim_init_mg_to_lsm(self, cim_init_mg, system_id):
+ ag_name = cim_init_mg['ElementName']
+ ag_id = md5(cim_init_mg['InstanceID'])
+ cim_init_pros = self._property_list_of_id('Initiator')
+ cim_init_pros.extend(['IDType'])
+ cim_inits = self._cim_init_of_init_mg(cim_init_mg.path, cim_init_pros)
+ ag_init_ids = [self._init_id(i) for i in cim_inits]
+ init_type = Smis._init_type_of_cim_inits(cim_inits)
+ return AccessGroup(ag_id, ag_name, ag_init_ids, init_type, system_id)
+
+ def _wait_invoke(self, rc, out, out_key=None, expect_class=None,
+ flag_out_array=False,):
+ """
+ Return out[out_key] if found rc == INVOKE_OK.
+ For rc == INVOKE_ASYNC, we check every Smis.INVOKE_CHECK_INTERVAL
+ seconds until done. Then return assocition via CIM_AffectedJobElement
+ Return CIM_InstanceName
+ Assuming only one CIM_InstanceName will get.
+ """
+ if rc == Smis.INVOKE_OK:
+ if out_key is None:
+ return None
+ if out_key in out:
+ if flag_out_array:
+ if len(out[out_key]) != 1:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_wait_invoke(), %s is not length 1: %s"
+ % (out_key, out.items()))
+ return out[out_key][0]
+ return out[out_key]
+ else:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_wait_invoke(), %s not exist in out %s" %
+ (out_key, out.items()))
+ elif rc == Smis.INVOKE_ASYNC:
+ cim_job_path = out['Job']
+ loop_counter = 0
+ job_pros = ['JobState', 'PercentComplete', 'ErrorDescription',
+ 'OperationalStatus']
+ cim_xxxs_path = []
+ while(loop_counter <= Smis._INVOKE_MAX_LOOP_COUNT):
+ cim_job = self._c.GetInstance(cim_job_path,
+ PropertyList=job_pros,
+ LocalOnly=False)
+ job_state = cim_job['JobState']
+ if job_state in (Smis.JS_NEW, Smis.JS_STARTING,
+ Smis.JS_RUNNING):
+ loop_counter = loop_counter + 1
+ time.sleep(Smis._INVOKE_CHECK_INTERVAL)
+ continue
+ elif job_state == Smis.JS_COMPLETED:
+ if expect_class is None:
+ return None
+ cim_xxxs_path = self._c.AssociatorNames(
+ cim_job.path,
+ AssocClass='CIM_AffectedJobElement',
+ ResultClass=expect_class)
+ else:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_wait_invoke(): Got unknown job state "
+ "%d: %s" % (job_state, cim_job.items()))
+ if len(cim_xxxs_path) != 1:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_wait_invoke(): got unexpect(not 1) "
+ "return from CIM_AffectedJobElement: "
+ "%s, out: %s, job: %s" %
+ (cim_xxxs_path, out.items(),
+ cim_job.items()))
+ return cim_xxxs_path[0]
+ else:
+ raise LsmError(ErrorNumber.LSM_PLUGIN_BUG,
+ "_wait_invoke(): Got unexpected rc code "
+ "%d, out: %s" % (rc, out.items()))
+
+ def _cim_pep_path_of_fc_tgt(self, cim_fc_tgt_path):
+ """
+ Return CIMInstanceName of CIM_SCSIProtocolEndpoint of CIM_FCPort
+ In 1.4r6, it's one-to-one map.
+ """
+ return self._c.AssociatorNames(
+ cim_fc_tgt_path,
+ AssocClass='CIM_DeviceSAPImplementation',
+ ResultClass='CIM_SCSIProtocolEndpoint')[0]
+
+ def _check_exist_cim_tgt_mg(self, name):
+ """
+ We should do more checks[1] in stead of use it directly.
+ But considering EMC VMAX is the only support vendor, make it quick
+ and works could be priority 1.
+ We can improve this for any bug report.
+
+ [1] At least check whether CIM_TargetMaskingGroup is already used
+ by other SPC.
+ """
+ cim_tgt_mgs = self._enumerate(
+ class_name='CIM_TargetMaskingGroup',
+ property_list=['ElementName'])
+ for cim_tgt_mg in cim_tgt_mgs:
+ if cim_tgt_mg['ElementName'] == name:
+ return cim_tgt_mg.path
+
+ return None
+
+ def _check_exist_cim_dev_mg(self, name, cim_gmm_path, cim_vol_path,
+ vol_id):
+ """
+ This is buggy check, but it works on EMC VMAX which is only supported
+ platform of Group Masking and Mapping.
+ When found CIM_DeviceMaskingGroup, make sure cim_vol is included.
+ """
+ cim_dev_mgs = self._enumerate(
+ class_name='CIM_DeviceMaskingGroup',
+ property_list=['ElementName'])
+ cim_dev_mg = None
+ for tmp_cim_dev_mg in cim_dev_mgs:
+ if tmp_cim_dev_mg['ElementName'] == name:
+ cim_dev_mg = tmp_cim_dev_mg
+ break
+ if cim_dev_mg:
+ # Check whether cim_vol included.
+ cim_vol_pros = self._property_list_of_id('Volume')
+ cim_vols = self._c.Associators(
+ cim_dev_mg.path,
+ AssocClass='CIM_OrderedMemberOfCollection',
+ ResultClass='CIM_StorageVolume',
+ PropertyList=cim_vol_pros)
+ for cim_vol in cim_vols:
+ if self._vol_id(cim_vol) == vol_id:
+ return cim_dev_mg.path
+
+ # We should add this volume to found DeviceMaskingGroup
+ in_params = {
+ 'MaskingGroup': cim_dev_mg.path,
+ 'Members': [cim_vol_path],
+ }
+ (rc, out) = self._c.InvokeMethod(
+ 'AddMembers',
+ cim_gmm_path, **in_params)
+ self._wait_invoke(rc, out)
+ return cim_dev_mg.path
+
+ return None
+
+ @handle_cim_errors
+ def access_group_create(self, name, init_id, init_type, system,
+ flags=0):
+ """
+ Using 1.5.0 'Group Masking and Mapping' profile.
+ Actually, only EMC VMAX/DMX support this now(July 2014).
+ Steps:
+ 0. Check exist SPC of init_id for duplication call and
+ confliction.
+ 1. Create CIM_InitiatorMaskingGroup
+ """
+ if self.fallback_mode:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "access_group_create() is not supported in "
+ "fallback mode")
+
+ self._profile_is_supported(SNIA.GROUP_MASK_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_5,
+ strict=False,
+ raise_error=True)
+
+ if init_type != AccessGroup.INIT_TYPE_WWPN and \
+ init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "SMI-S plugin only support creating FC/FCoE WWPN "
+ "and iSCSI AccessGroup")
+
+ flag_fc_support = self._fc_tgt_is_supported()
+ flag_iscsi_support = self._iscsi_tgt_is_supported()
+
+ if init_type == AccessGroup.INIT_TYPE_WWPN and not flag_fc_support:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "Target SMI-S provider does not support "
+ "FC target port, which not allow creating "
+ "WWPN access group")
+
+ if init_type == AccessGroup.INIT_TYPE_ISCSI_IQN and \
+ not flag_iscsi_support:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "Target SMI-S provider does not support "
+ "iSCSI target port, which not allow creating "
+ "iSCSI IQN access group")
+
+ cim_sys = self._get_cim_instance_by_id('System', system.id)
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ # EMC VNX/CX does not support Group M&M, which incorrectly exposed
+ # in CIM_RegisteredProfile
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "access_group_create() is not supported by "
+ "EMC VNX/CX which lacks the support of SNIA 1.5+ "
+ "Group Masking and Mapping profile")
+
+ cim_init = self._cim_init_check_or_create(
+ cim_sys.path, init_id, init_type)
+
+ cim_init_mg_pros = self._cim_init_mg_pros()
+ exist_cim_init_mgs = self._c.Associators(
+ cim_init.path,
+ AssocClass='CIM_MemberOfCollection',
+ ResultClass='CIM_InitiatorMaskingGroup',
+ PropertyList=cim_init_mg_pros)
+
+ if len(exist_cim_init_mgs) != 0:
+ for exist_cim_init_mg in exist_cim_init_mgs:
+ if exist_cim_init_mg['ElementName'] == name:
+ return self._cim_init_mg_to_lsm(
+ exist_cim_init_mg, system.id)
+
+ # Name does not match.
+ raise LsmError(ErrorNumber.EXISTS_INITIATOR,
+ "Initiator %s already exist in other access group "
+ % init_id +
+ "with name %s and ID: %s" %
+ (exist_cim_init_mgs[0]['ElementName'],
+ md5(exist_cim_init_mgs[0]['DeviceID'])))
+
+ # Create CIM_InitiatorMaskingGroup
+ cim_gmm_path = self._get_cim_service_path(
+ cim_sys.path, 'CIM_GroupMaskingMappingService')
+
+ in_params = {'GroupName': name,
+ 'Members': [cim_init.path],
+ 'Type': DMTF.MASK_GROUP_TYPE_INIT}
+
+ (rc, out) = self._c.InvokeMethod(
+ 'CreateGroup', cim_gmm_path, **in_params)
+
+ cim_init_mg_path = self._wait_invoke(
+ rc, out, out_key='MaskingGroup',
+ expect_class='CIM_InitiatorMaskingGroup')
+
+ cim_init_mg = self._c.GetInstance(
+ cim_init_mg_path, PropertyList=cim_init_mg_pros, LocalOnly=False)
+ return self._cim_init_mg_to_lsm(cim_init_mg, system.id)
+
+ def access_group_delete(self, access_group, flags=0):
+ if self.fallback_mode:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "access_group_create() is not supported in "
+ "fallback mode")
+
+ self._profile_is_supported(SNIA.GROUP_MASK_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_5,
+ strict=False,
+ raise_error=True)
+
+ cim_init_mg = self._cim_init_mg_of_id(
+ access_group.id, raise_error=True)
+
+ # Check whether still have volume masked.
+ cim_spcs_path = self._c.AssociatorNames(
+ cim_init_mg.path,
+ AssocClass='CIM_AssociatedInitiatorMaskingGroup',
+ ResultClass='CIM_SCSIProtocolController')
+
+ for cim_spc_path in cim_spcs_path:
+ if len(self._c.AssociatorNames(
+ cim_spc_path,
+ AssocClass='CIM_ProtocolControllerForUnit',
+ ResultClass='CIM_StorageVolume')) >= 1:
+ raise LsmError(ErrorNumber.MASKED_ACCESS_GROUP,
+ "Access Group %s has volume masked" %
+ access_group.id)
+
+ cim_gmm_path = self._c.AssociatorNames(
+ cim_init_mg.path,
+ AssocClass='CIM_ServiceAffectsElement',
+ ResultClass='CIM_GroupMaskingMappingService')[0]
+
+ in_params={
+ 'MaskingGroup': cim_init_mg.path,
+ 'Force': True,
+ }
+
+ (rc, out) = self._c.InvokeMethod('DeleteGroup', cim_gmm_path,
+ **in_params)
+
+ self._wait_invoke(rc, out)
+ return None
--
1.8.3.1
Tony Asleson
2014-07-28 22:25:34 UTC
Permalink
Post by Gris Ge
CIM_SCSIProtocolController
# For fallback or only support 'Masking and Mapping' profile
# arrays. In real world, it means most of arrays.
CIM_InitiatorMaskingGroup
# For 1.5+ 'Group Masking and Mapping' profile.
# In real world, it means EMC VMAX only.
# A EMC Bug found on EMC VNX/CX which incorrect indicate
# support of Group M&M profile, bug reported and work around
# implemented.
# The reason to do this is because EMC VMAX does not allow to create
# SPC with no volume masked.
# Actually, 'Group M&M' profile is clearer and easier for use than
# old 'M&M' profile. I will try to beg vendors to support that profile.
2. Fix old access_group_initiator_add() and
access_group_initiator_delete() to return None instead of job id.
access_group_create()
access_group_delete()
1. Use _mask_type() to indicate the support of Group M&M.
volume_mask()
volume_unmask()
access_groups()
access_group_initiator_add()
access_group_initiator_delete()
a InitiatorMaskingGroup
# Containing Initiators.
b TargetMaskingGroup
# Containing target ports. Currently we expose to all ports.
c DeviceMaskingGroup
# Containing masked volumes.
4. New method _wait_invoke() to allow wait cim_job done.
access_group_initiator_add()
# EMC VNX/CX require WWNN define when add initiator which is not
# supported by LSM yet. We have no intention to do so, WWPN should
# the only identifier for FC and FCoE, we should not confuse user
# with one vendor extension.
The current code in master passed on initiator add/remove, this is
because we were only using CIM_SCSIProtocolController then, correct? We
didn't need to provide the node name.
Post by Gris Ge
volume_mask()
volume_unmask()
access_groups()
access_group_initiator_add()
access_group_initiator_delete()
access_group_create()
access_group_delete()
The automated unit test is not testing these because they show as
unsupported when we ask for capabilities, please add.
Post by Gris Ge
volume_mask()
volume_unmask()
access_groups()
Thanks,
Tony
Gris Ge
2014-07-29 01:37:51 UTC
Permalink
Post by Tony Asleson
The current code in master passed on initiator add/remove, this is
because we were only using CIM_SCSIProtocolController then, correct? We
didn't need to provide the node name.
Before calling ExposePath, we need to make sure CIM_StorageHardwareID
is created. When creating CIM_StorageHardwareID, EMC VNX/CX require WWPN
to do so which is not supported by LSM yet.

Quote from EMC SMI-S developer guide 4.6:
"""
Note: The Node WWN is significant for the CLARiiON only. For Fibre Channel
StorageHardwareID initiators, the CLARiiON uniquely identifies an HBA
registration through the Node and Port WWN (this is the HBAUID). If the HBA
registration is to be added manually using the technique described in this
section, the CIM client must know the exact Node and Port WWN when calling
CreateStorageHardwareID(). If the CIM client just guesses, hoping to adjust it
later, the provisioning steps made will not work once the host containing the
actual HBA hardware is available on the SAN that contains the CLARiiON array.
Consult with your SAN administrator to obtain the exact Node and Port WWN.
"""

In might be possible, user are requesting access_group_initiator_add() against
pre-defined CIM_StorageHardwareID which is already created by SAN administrator
via EMC vendor tools. In that case access_group_initiator_add() should work,
but we raise 'NO_SUPPORT' anyway. Two reason for this:

1. No public call to determine the existence of CIM_StorageHardwareID.
2. To make API call simple. EMC VNX/CX is the only product require WWNN.
Post by Tony Asleson
The automated unit test is not testing these because they show as
unsupported when we ask for capabilities, please add.
Will do.
--
Gris Ge
Tony Asleson
2014-07-28 21:36:28 UTC
Permalink
Post by Gris Ge
To simplify LSM user and plugin implementation, I would like to restrict
1. access_group_initiator_delete()
Refuse to delete the last initiator from access group which has
volume masked.
Some plugin(SMI-S for EMC VMAX) does not allow keeping volume masking
information when no member in access group.
2. volume_mask()
Refuse to do volume masking against empty(no member) access group.
The same reason as previous one.
Refuse to delete a access group which has volume masked.
These seem reasonable, however we will need to go through the other
plug-ins and do precondition checks to enforce these. Other arrays
allows you to do some of these things IIRC.

Thanks,
Tony
Post by Gris Ge
---
python_binding/lsm/_common.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index 1f52542..fdd10ab 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
DISK_BUSY = 500
VOLUME_BUSY = 501
+ ACCESS_GROUP_BUSY = 502 # refuse to remove the last initiator from
+ # access group which have volume masked.
UNSUPPORTED_SEARCH_KEY = 510
+ EMPTY_ACCESS_GROUP = 511 # volume_mask() will fail if access group
+ # has no member/initiator.
+
+ MASKED_ACCESS_GROUP = 512 # access_group_delete() will fail if
+ # have volume masked.
+
_LOCALS = locals()
@staticmethod
Loading...