Discussion:
[Libstoragemgmt-devel] [PATCH 1/8] python library: lsm.Capacities changes
Gris Ge
2014-07-29 15:18:03 UTC
Permalink
* Remove of these capacities:
BLOCK_SUPPORT
FS_SUPPORT
# We don't actually need them.

* Renamed these capabilities:
ACCESS_GROUP_CREATE -> ACCESS_GROUP_CREATE_WWPN
ACCESS_GROUP_INITIATOR_ADD -> ACCESS_GROUP_INITIATOR_ADD_WWPN
FS_SNAPSHOT_REVERT -> FS_SNAPSHOT_RESTORE
FS_SNAPSHOT_REVERT_SPECIFIC_FILES -> FS_SNAPSHOT_RESTORE_SPECIFIC_FILES
# To match the name of lsm.Client.fs_snapshot_restore()

* Added these capabilities:
ACCESS_GROUP_CREATE_ISCSI_IQN
ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN
ACCESS_GROUP_INITIATOR_ADD_MIX
# _ADD_MIX means can add different type of init_id into access group.
# _ADD_ISCSI_IQN and _ADD_WWPN indicate the types supported.
# Useful when access group is empty.

* Added public methods:
lsm.Capabilities.get_supported()
# Return a list of integer with supported capabilities.
lsm.Capabilities.valid_capabilities()
# Return a list of integer with all valid capabilities(support or not
# support).
lsm.Capabilities.lsm_cap_to_str()
# Convert LSM capability integer into string.
# This save the coding workload of lsmcli.

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm/_data.py | 73 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 64 insertions(+), 9 deletions(-)

diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index 8fd42a3..38d91dc 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -743,11 +743,9 @@ class Capabilities(IData):
SUPPORTED # Supported
) = (0, 1)

- _NUM = 512
+ _NUM = 512 # Indicate the maximum capability integer

- #Array wide
- BLOCK_SUPPORT = 0 # Array handles block operations
- FS_SUPPORT = 1 # Array handles file system
+ _CAP_NUM_BEGIN = 20 # Indicate the first capability integer

#Block operations
VOLUMES = 20
@@ -773,9 +771,10 @@ class Capabilities(IData):
VOLUME_MASK = 36
VOLUME_UNMASK = 37
ACCESS_GROUPS = 38
- ACCESS_GROUP_CREATE = 39
+ ACCESS_GROUP_CREATE_WWPN = 39
ACCESS_GROUP_DELETE = 40
- ACCESS_GROUP_INITIATOR_ADD = 41
+ ACCESS_GROUP_INITIATOR_ADD_WWPN = 41
+ # For empty access group, this indicate it can add WWPN into it.
ACCESS_GROUP_INITIATOR_DELETE = 42

VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
@@ -784,6 +783,13 @@ class Capabilities(IData):
VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46

+ ACCESS_GROUP_CREATE_ISCSI_IQN = 47
+ ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN = 48
+ # For empty access group, this indicate it can add iSCSI IQN into it.
+
+ ACCESS_GROUP_INITIATOR_ADD_MIX = 49
+ # Allowing add different initiator type than existing one
+
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53

VOLUME_THIN = 55
@@ -798,8 +804,8 @@ class Capabilities(IData):
FS_SNAPSHOTS = 106
FS_SNAPSHOT_CREATE = 107
FS_SNAPSHOT_DELETE = 109
- FS_SNAPSHOT_REVERT = 110
- FS_SNAPSHOT_REVERT_SPECIFIC_FILES = 111
+ FS_SNAPSHOT_RESTORE = 110
+ FS_SNAPSHOT_RESTORE_SPECIFIC_FILES = 111
FS_CHILD_DEPENDENCY = 112
FS_CHILD_DEPENDENCY_RM = 113
FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES = 114
@@ -840,9 +846,11 @@ class Capabilities(IData):
ACCESS_GROUPS_QUICK_SEARCH = 213
FS_QUICK_SEARCH = 214
NFS_EXPORTS_QUICK_SEARCH = 215
- TARGET_PORTS = 215
+ TARGET_PORTS = 216
TARGET_PORTS_QUICK_SEARCH = 217

+ DISKS = 220
+
def _to_dict(self):
rc = {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
@@ -864,6 +872,53 @@ class Capabilities(IData):
return Capabilities.UNSUPPORTED
return self._cap[capability]

+ @staticmethod
+ def _lsm_cap_to_str_dict():
+ """
+ Return a dict containing all valid capability:
+ integer => string name
+ """
+ lsm_cap_to_str_conv = dict()
+ for cap_str, cap_int in Capabilities.__dict__.items():
+ if type(cap_str) == str and \
+ type(cap_int) == int and \
+ cap_str[0] != '_' and \
+ cap_int >= Capabilities._CAP_NUM_BEGIN and \
+ cap_int <= Capabilities._NUM:
+ lsm_cap_to_str_conv[cap_int] = cap_str
+ return lsm_cap_to_str_conv
+
+ def get_supported(self):
+ """
+ Return a list of integer containing only the supported capabilities
+ """
+ rc = []
+ lsm_cap_to_str_conv = Capabilities._lsm_cap_to_str_dict()
+ for cap_int in lsm_cap_to_str_conv.keys():
+ if self._cap[cap_int] == Capabilities.SUPPORTED:
+ rc.append(cap_int)
+ return sorted(rc)
+
+ @staticmethod
+ def valid_capabilities():
+ """
+ Retrun a list of integer containing valid capabilities.
+ """
+ lsm_cap_to_str_conv = Capabilities._lsm_cap_to_str_dict()
+ return sorted(lsm_cap_to_str_conv.keys())
+
+ @staticmethod
+ def lsm_cap_to_str(lsm_cap):
+ """
+ Convert capability integer to string(constant name)
+ Return None if lsm_cap is not a valid capability.
+ """
+ lsm_cap_to_str_conv = Capabilities._lsm_cap_to_str_dict()
+ try:
+ return lsm_cap_to_str_conv[lsm_cap]
+ except KeyError:
+ return None
+
def set(self, capability, value=SUPPORTED):
self._cap[capability] = value
return None
--
1.8.3.1
Gris Ge
2014-07-29 15:18:04 UTC
Permalink
* cmdtest.py
1. Fix incorrect strip in parse().
2. Sync with changes of capabilities.
3. Use random_iqn() when creating access group.
# Simulator has more strict checking now.

* plugin_test.py
1. Sync with changes of capabilities.
2. Choose non-empty access group for volume mask test.

Signed-off-by: Gris Ge <***@redhat.com>
---
test/cmdtest.py | 22 +++++++------
test/plugin_test.py | 89 +++++++++++++++++++++++++++++++++--------------------
2 files changed, 68 insertions(+), 43 deletions(-)

diff --git a/test/cmdtest.py b/test/cmdtest.py
index b394a62..f63cc4a 100755
--- a/test/cmdtest.py
+++ b/test/cmdtest.py
@@ -110,12 +110,13 @@ def parse(out):
rc = []
for line in out.split('\n'):
elem = line.split(sep)
-
+ cleaned_elem = []
for e in elem:
e = e.strip()
+ cleaned_elem.append(e)

- if len(elem) > 1:
- rc.append(elem)
+ if len(cleaned_elem) > 1:
+ rc.append(cleaned_elem)
return rc


@@ -439,12 +440,12 @@ def test_display(cap, system_id):
status for each of them
"""
to_test = ['SYSTEMS']
+ to_test.append('POOLS')

- if cap['BLOCK_SUPPORT']:
- to_test.append('POOLS')
+ if cap['VOLUMES']:
to_test.append('VOLUMES')

- if cap['FS_SUPPORT'] and cap['FS']:
+ if cap['FS']:
to_test.append("FS")

if cap['EXPORTS']:
@@ -589,10 +590,10 @@ def test_mapping(cap, system_id):
iqn1 = random_iqn()
iqn2 = random_iqn()

- if cap['ACCESS_GROUP_CREATE']:
+ if cap['ACCESS_GROUP_CREATE_ISCSI_IQN']:
ag_id = access_group_create(iqn1, system_id)

- if cap['ACCESS_GROUP_ADD_INITIATOR']:
+ if cap['ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN']:
access_group_initiator_add(ag_id, iqn2)

if cap['VOLUME_MASK'] and cap['VOLUME_UNMASK']:
@@ -613,7 +614,7 @@ def test_mapping(cap, system_id):
if cap['VOLUME_DELETE']:
volume_delete(vol_id)

- if cap['ACCESS_GROUP_DEL_INITIATOR']:
+ if cap['ACCESS_GROUP_INITIATOR_DELETE']:
access_group_remove_init(ag_id, iqn1)
access_group_remove_init(ag_id, iqn2)

@@ -648,6 +649,7 @@ def create_all(cap, system_id):
test_fs_creation(cap, system_id)
test_nfs(cap, system_id)

+
def search_test(cap, system_id):
print "\nTesting query with search ID\n"
sys_id_filter = "--sys='%s'" % system_id
@@ -662,7 +664,7 @@ def search_test(cap, system_id):

disk_id_filter = "--disk='%s'" % test_disk_id

- ag_id = access_group_create(iqn[0], system_id)
+ ag_id = access_group_create(random_iqn(), system_id)
ag_id_filter = "--ag='%s'" % ag_id

fs_id = fs_create(pool_id)
diff --git a/test/plugin_test.py b/test/plugin_test.py
index 0731e25..d60b965 100755
--- a/test/plugin_test.py
+++ b/test/plugin_test.py
@@ -614,7 +614,14 @@ class TestPlugin(unittest.TestCase):
if len(ag_list):
vol = self._volume_create(s.id)[0]
self.assertTrue(vol is not None)
- ag = ag_list[0]
+ chose_ag = None
+ for ag in ag_list:
+ if len(ag.init_ids) >= 1:
+ chose_ag = ag
+ break
+ if chose_ag is None:
+ raise Exception("No access group with 1+ member "
+ "found, cannot do volume mask test")

if vol is not None:
self.c.volume_mask(ag, vol)
@@ -623,31 +630,22 @@ class TestPlugin(unittest.TestCase):
self._masking_state(cap, ag, vol, False)
self._volume_delete(vol)

- def _create_access_group(self, cap, s):
+ def _create_access_group(self, cap, s, init_type):
ag_created = None

- # Without this information we would need to systematically go through
- # different port types trying to create an access group, which we
- # can do, but not until we need too.
- if not supported(cap, [lsm.Capabilities.TARGET_PORTS]):
- return None
-
- tps = self.c.target_ports('system_id', s.id)
- if len(tps):
- tp = tps[0]
-
- if tp.port_type == lsm.TargetPort.PORT_TYPE_FC:
- ag_created = self.c.access_group_create(
- rs('access_group'),
- '500A0986994B8DC5',
- lsm.AccessGroup.INIT_TYPE_WWPN, s.id)
- if tp.port_type == lsm.TargetPort.PORT_TYPE_ISCSI:
- ag_created = self.c.access_group_create(
- rs('access_group'),
- 'iqn.1994-05.com.domain:01.89bd01',
- lsm.AccessGroup.INIT_TYPE_ISCSI_IQN, s.id)
-
- self.assertTrue(ag_created is not None)
+ if init_type == lsm.AccessGroup.INIT_TYPE_ISCSI_IQN:
+ ag_created = self.c.access_group_create(
+ rs('access_group'),
+ 'iqn.1994-05.com.domain:01.89bd01',
+ lsm.AccessGroup.INIT_TYPE_ISCSI_IQN, s)
+
+ elif init_type == lsm.AccessGroup.INIT_TYPE_WWPN:
+ ag_created = self.c.access_group_create(
+ rs('access_group'),
+ '500A0986994B8DC5',
+ lsm.AccessGroup.INIT_TYPE_WWPN, s)
+
+ self.assertTrue(ag_created is not None)

if ag_created is not None:
ag_list = self.c.access_groups()
@@ -667,11 +665,21 @@ class TestPlugin(unittest.TestCase):
"access group list!")

def _test_ag_create_delete(self, cap, s):
+ ag = None
if supported(cap, [lsm.Capabilities.ACCESS_GROUPS,
- lsm.Capabilities.ACCESS_GROUP_CREATE]):
- ag = self._create_access_group(cap, s)
+ lsm.Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN]):
+ ag = self._create_access_group(
+ cap, s, lsm.AccessGroup.INIT_TYPE_ISCSI_IQN)
if ag is not None and \
- supported(cap, [lsm.Capabilities.ACCESS_GROUP_DELETE]):
+ supported(cap, [lsm.Capabilities.ACCESS_GROUP_DELETE]):
+ self._delete_access_group(ag)
+
+ if supported(cap, [lsm.Capabilities.ACCESS_GROUPS,
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWPN]):
+ ag = self._create_access_group(
+ cap, s, lsm.AccessGroup.INIT_TYPE_WWPN)
+ if ag is not None and \
+ supported(cap, [lsm.Capabilities.ACCESS_GROUP_DELETE]):
self._delete_access_group(ag)

def test_access_group_create_delete(self):
@@ -729,11 +737,19 @@ class TestPlugin(unittest.TestCase):
if supported(cap, [lsm.Capabilities.ACCESS_GROUPS]):
ag_list = self.c.access_groups('system_id', s.id)

- if len(ag_list) == 0 and \
- supported(cap, [lsm.Capabilities.ACCESS_GROUP_CREATE,
- lsm.Capabilities.ACCESS_GROUP_DELETE]):
- ag_to_delete = self._create_access_group(cap, s)
- ag_list = self.c.access_groups('system_id', s.id)
+ if len(ag_list) == 0:
+ if supported(
+ cap, [lsm.Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN,
+ lsm.Capabilities.ACCESS_GROUP_DELETE]):
+ ag_to_delete = self._create_access_group(
+ cap, s, lsm.AccessGroup.INIT_TYPE_ISCSI_IQN)
+ ag_list = self.c.access_groups('system_id', s.id)
+ if supported(
+ cap, [lsm.Capabilities.ACCESS_GROUP_CREATE_WWPN,
+ lsm.Capabilities.ACCESS_GROUP_DELETE]):
+ ag_to_delete = self._create_access_group(
+ cap, s, lsm.AccessGroup.INIT_TYPE_WWPN)
+ ag_list = self.c.access_groups('system_id', s.id)

if len(ag_list):
# Try and find an initiator group that has a usable access
@@ -745,7 +761,14 @@ class TestPlugin(unittest.TestCase):
break

if supported(cap, [lsm.Capabilities.
- ACCESS_GROUP_INITIATOR_ADD]):
+ ACCESS_GROUP_INITIATOR_ADD_WWPN]):
+ init_id = self._ag_init_add(ag)
+ if supported(cap, [lsm.Capabilities.
+ ACCESS_GROUP_INITIATOR_DELETE]):
+ self._ag_init_delete(ag, init_id)
+
+ if supported(cap, [lsm.Capabilities.
+ ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN]):
init_id = self._ag_init_add(ag)
if supported(cap, [lsm.Capabilities.
ACCESS_GROUP_INITIATOR_DELETE]):
--
1.8.3.1
Gris Ge
2014-07-29 15:18:06 UTC
Permalink
* Sync with access group capabilities changes.
* 7-Mode ONTAP does not support MIX type of access group.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/ontap/ontap.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index 33525a2..fb0244d 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -418,9 +418,11 @@ class Ontap(IStorageAreaNetwork, INfs):
cap.set(Capabilities.VOLUME_MASK)
cap.set(Capabilities.VOLUME_UNMASK)
cap.set(Capabilities.ACCESS_GROUPS)
- cap.set(Capabilities.ACCESS_GROUP_CREATE)
+ cap.set(Capabilities.ACCESS_GROUP_CREATE_WWPN)
+ cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
cap.set(Capabilities.ACCESS_GROUP_DELETE)
- cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD)
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_WWPN)
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_IQN)
cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
@@ -784,7 +786,6 @@ class Ontap(IStorageAreaNetwork, INfs):

return self._access_group(na_ags[0])

-
@handle_ontap_errors
def volumes_accessible_by_access_group(self, access_group, flags=0):
rc = []
--
1.8.3.1
Gris Ge
2014-07-29 15:18:05 UTC
Permalink
* PEP8 clean up.
* Use lsm.Capabilities.lsm_cap_to_str() to replace the old code.
* Use lsm.lsmcli.data_display.DisplayData.display_data_script_way() for
displaying capabilities.

Signed-off-by: Gris Ge <***@redhat.com>
---
tools/lsmcli/cmdline.py | 117 +++++++------------------------------------
tools/lsmcli/data_display.py | 4 +-
2 files changed, 21 insertions(+), 100 deletions(-)

diff --git a/tools/lsmcli/cmdline.py b/tools/lsmcli/cmdline.py
index 0af2034..244b5cd 100644
--- a/tools/lsmcli/cmdline.py
+++ b/tools/lsmcli/cmdline.py
@@ -22,6 +22,7 @@ import getpass
import time
import tty
import termios
+from collections import OrderedDict

from argparse import ArgumentParser
from argparse import RawTextHelpFormatter
@@ -914,8 +915,8 @@ class CmdLine:
self.c.access_groups_granted_to_volume(lsm_vol))
else:
return self.display_data([])
- elif search_key and \
- search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS:
+ elif (search_key and
+ search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS):
raise ArgError("Search key '%s' is not supported by "
"Access Group listing" % search_key)
self.display_data(
@@ -1076,102 +1077,22 @@ class CmdLine:
s = _get_item(self.c.systems(), args.sys, "system id")

cap = self.c.capabilities(s)
- self._cp("BLOCK_SUPPORT", cap.supported(Capabilities.BLOCK_SUPPORT))
- self._cp("FS_SUPPORT", cap.supported(Capabilities.FS_SUPPORT))
- self._cp("VOLUMES", cap.supported(Capabilities.VOLUMES))
- self._cp("VOLUME_CREATE", cap.supported(Capabilities.VOLUME_CREATE))
- self._cp("VOLUME_RESIZE", cap.supported(Capabilities.VOLUME_RESIZE))
- self._cp("VOLUME_REPLICATE",
- cap.supported(Capabilities.VOLUME_REPLICATE))
- self._cp("VOLUME_REPLICATE_CLONE",
- cap.supported(Capabilities.VOLUME_REPLICATE_CLONE))
- self._cp("VOLUME_REPLICATE_COPY",
- cap.supported(Capabilities.VOLUME_REPLICATE_COPY))
- self._cp("VOLUME_REPLICATE_MIRROR_ASYNC",
- cap.supported(Capabilities.VOLUME_REPLICATE_MIRROR_ASYNC))
- self._cp("VOLUME_REPLICATE_MIRROR_SYNC",
- cap.supported(Capabilities.VOLUME_REPLICATE_MIRROR_SYNC))
- self._cp("VOLUME_COPY_RANGE_BLOCK_SIZE",
- cap.supported(Capabilities.VOLUME_COPY_RANGE_BLOCK_SIZE))
- self._cp("VOLUME_COPY_RANGE",
- cap.supported(Capabilities.VOLUME_COPY_RANGE))
- self._cp("VOLUME_COPY_RANGE_CLONE",
- cap.supported(Capabilities.VOLUME_COPY_RANGE_CLONE))
- self._cp("VOLUME_COPY_RANGE_COPY",
- cap.supported(Capabilities.VOLUME_COPY_RANGE_COPY))
- self._cp("VOLUME_DELETE", cap.supported(Capabilities.VOLUME_DELETE))
- self._cp("VOLUME_ONLINE", cap.supported(Capabilities.VOLUME_ONLINE))
- self._cp("VOLUME_OFFLINE", cap.supported(Capabilities.VOLUME_OFFLINE))
- self._cp("VOLUME_THIN",
- cap.supported(Capabilities.VOLUME_THIN))
- self._cp("VOLUME_ISCSI_CHAP_AUTHENTICATION",
- cap.supported(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION))
- self._cp("VOLUME_MASK",
- cap.supported(Capabilities.VOLUME_MASK))
- self._cp("VOLUME_UNMASK",
- cap.supported(Capabilities.VOLUME_UNMASK))
- self._cp("ACCESS_GROUPS",
- cap.supported(Capabilities.ACCESS_GROUPS))
- self._cp("ACCESS_GROUP_CREATE",
- cap.supported(Capabilities.ACCESS_GROUP_CREATE))
- self._cp("ACCESS_GROUP_DELETE",
- cap.supported(Capabilities.ACCESS_GROUP_DELETE))
- self._cp("ACCESS_GROUP_ADD_INITIATOR",
- cap.supported(Capabilities.ACCESS_GROUP_INITIATOR_ADD))
- self._cp("ACCESS_GROUP_DEL_INITIATOR",
- cap.supported(Capabilities.ACCESS_GROUP_INITIATOR_DELETE))
- self._cp("VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP",
- cap.supported(
- Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP))
- self._cp("ACCESS_GROUPS_GRANTED_TO_VOLUME",
- cap.supported(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME))
- self._cp("VOLUME_CHILD_DEPENDENCY",
- cap.supported(Capabilities.VOLUME_CHILD_DEPENDENCY))
- self._cp("VOLUME_CHILD_DEPENDENCY_RM",
- cap.supported(Capabilities.VOLUME_CHILD_DEPENDENCY_RM))
- self._cp("FS", cap.supported(Capabilities.FS))
- self._cp("FS_DELETE", cap.supported(Capabilities.FS_DELETE))
- self._cp("FS_RESIZE", cap.supported(Capabilities.FS_RESIZE))
- self._cp("FS_CREATE", cap.supported(Capabilities.FS_CREATE))
- self._cp("FS_CLONE", cap.supported(Capabilities.FS_CLONE))
- self._cp("FILE_CLONE", cap.supported(Capabilities.FILE_CLONE))
- self._cp("FS_SNAPSHOTS", cap.supported(Capabilities.FS_SNAPSHOTS))
- self._cp("FS_SNAPSHOT_CREATE",
- cap.supported(Capabilities.FS_SNAPSHOT_CREATE))
- self._cp("FS_SNAPSHOT_DELETE",
- cap.supported(Capabilities.FS_SNAPSHOT_DELETE))
- self._cp("FS_SNAPSHOT_REVERT",
- cap.supported(Capabilities.FS_SNAPSHOT_REVERT))
- self._cp("FS_SNAPSHOT_REVERT_SPECIFIC_FILES",
- cap.supported(Capabilities.FS_SNAPSHOT_REVERT_SPECIFIC_FILES))
- self._cp("FS_CHILD_DEPENDENCY",
- cap.supported(Capabilities.FS_CHILD_DEPENDENCY))
- self._cp("FS_CHILD_DEPENDENCY_RM",
- cap.supported(Capabilities.FS_CHILD_DEPENDENCY_RM))
- self._cp("FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES", cap.supported(
- Capabilities.FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES))
- self._cp("EXPORT_AUTH", cap.supported(Capabilities.EXPORT_AUTH))
- self._cp("EXPORTS", cap.supported(Capabilities.EXPORTS))
- self._cp("EXPORT_FS", cap.supported(Capabilities.EXPORT_FS))
- self._cp("EXPORT_REMOVE", cap.supported(Capabilities.EXPORT_REMOVE))
- self._cp("EXPORT_CUSTOM_PATH",
- cap.supported(Capabilities.EXPORT_CUSTOM_PATH))
- self._cp("POOLS_QUICK_SEARCH",
- cap.supported(Capabilities.POOLS_QUICK_SEARCH))
- self._cp("VOLUMES_QUICK_SEARCH",
- cap.supported(Capabilities.VOLUMES_QUICK_SEARCH))
- self._cp("DISKS_QUICK_SEARCH",
- cap.supported(Capabilities.DISKS_QUICK_SEARCH))
- self._cp("FS_QUICK_SEARCH",
- cap.supported(Capabilities.FS_QUICK_SEARCH))
- self._cp("ACCESS_GROUPS_QUICK_SEARCH",
- cap.supported(Capabilities.ACCESS_GROUPS_QUICK_SEARCH))
- self._cp("NFS_EXPORTS_QUICK_SEARCH",
- cap.supported(Capabilities.NFS_EXPORTS_QUICK_SEARCH))
- self._cp("TARGET_PORTS",
- cap.supported(Capabilities.TARGET_PORTS))
- self._cp("TARGET_PORTS_QUICK_SEARCH",
- cap.supported(Capabilities.TARGET_PORTS_QUICK_SEARCH))
+ sup_caps = cap.get_supported()
+ all_caps = cap.valid_capabilities()
+ sep = DisplayData.DEFAULT_SPLITTER
+ if self.args.sep is not None:
+ sep = self.args.sep
+
+ cap_data = OrderedDict()
+ # Show support capabilities first
+ for sup_cap in sup_caps:
+ cap_data[Capabilities.lsm_cap_to_str(sup_cap)] = 'SUPPORTED'
+
+ for cur_cap in all_caps:
+ if cur_cap not in sup_caps:
+ cap_data[Capabilities.lsm_cap_to_str(cur_cap)] = 'UNSUPPORTED'
+
+ DisplayData.display_data_script_way([cap_data], sep)

def plugin_info(self, args):
desc, version = self.c.plugin_info()
diff --git a/tools/lsmcli/data_display.py b/tools/lsmcli/data_display.py
index 2b8ac68..50d61c7 100644
--- a/tools/lsmcli/data_display.py
+++ b/tools/lsmcli/data_display.py
@@ -679,14 +679,14 @@ class DisplayData(object):
else:
return None
if display_way == DisplayData.DISPLAY_WAY_SCRIPT:
- DisplayData._display_data_script_way(data_dict_list, splitter)
+ DisplayData.display_data_script_way(data_dict_list, splitter)
elif display_way == DisplayData.DISPLAY_WAY_COLUMN:
DisplayData._display_data_column_way(
data_dict_list, splitter, flag_with_header)
return True

@staticmethod
- def _display_data_script_way(data_dict_list, splitter):
+ def display_data_script_way(data_dict_list, splitter):
key_column_width = 1
value_column_width = 1
--
1.8.3.1
Gris Ge
2014-07-29 15:18:08 UTC
Permalink
* Raise error on these conditions when access_group_create():
1. Duplicate name
2. Duplicate init_id
* Don't fail when got duplicate call of access_group_create().

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/sim/simarray.py | 44 +++++++++++++++++++++++++++++++++++---------
1 file changed, 35 insertions(+), 9 deletions(-)

diff --git a/plugin/sim/simarray.py b/plugin/sim/simarray.py
index 5d9cb6d..b399b03 100644
--- a/plugin/sim/simarray.py
+++ b/plugin/sim/simarray.py
@@ -1043,13 +1043,21 @@ class SimData(object):
def ags(self, flags=0):
return self.ag_dict.values()

- def _check_dup_init(self, init_id):
+ def _sim_ag_of_init(self, init_id):
+ """
+ Return sim_ag which containing this init_id.
+ If not found, return None
+ """
for sim_ag in self.ag_dict.values():
if init_id in sim_ag['init_ids']:
- raise LsmError(ErrorNumber.EXISTS_INITIATOR,
- "init_id %s already exist in other "
- % init_id +
- "access group %s" % sim_ag['ag_id'])
+ return sim_ag
+ return None
+
+ def _sim_ag_of_name(self, ag_name):
+ for sim_ag in self.ag_dict.values():
+ if ag_name == sim_ag['name']:
+ return sim_ag
+ return None

def _check_dup_name(self, sim_list, name, error_num):
used_names = [x['name'] for x in sim_list]
@@ -1057,9 +1065,27 @@ class SimData(object):
raise LsmError(error_num, "Name '%s' already in use" % name)

def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
- self._check_dup_name(
- self.ag_dict.values(), name, ErrorNumber.EXISTS_ACCESS_GROUP)
- self._check_dup_init(init_id)
+ exist_sim_ag = self._sim_ag_of_init(init_id)
+ if exist_sim_ag:
+ if exist_sim_ag['name'] == name:
+ return exist_sim_ag
+ else:
+ raise LsmError(ErrorNumber.EXISTS_INITIATOR,
+ "Initiator %s already exist in other " %
+ init_id + "access group %s(%s)" %
+ (exist_sim_ag['name'], exist_sim_ag['ag_id']))
+
+ exist_sim_ag = self._sim_ag_of_name(name)
+ if exist_sim_ag:
+ if init_id in exist_sim_ag['init_ids']:
+ return exist_sim_ag
+ else:
+ raise LsmError(ErrorNumber.EXISTS_ACCESS_GROUP,
+ "Another access group %s(%s) is using " %
+ (exist_sim_ag['name'], exist_sim_ag['ag_id']) +
+ "requested name %s but not contain init_id %s" %
+ (exist_sim_ag['name'], init_id))
+
sim_ag = dict()
sim_ag['init_ids'] = [init_id]
sim_ag['init_type'] = init_type
@@ -1083,7 +1109,7 @@ class SimData(object):
if init_id in self.ag_dict[ag_id]['init_ids']:
return self.ag_dict[ag_id]

- self._check_dup_init(init_id)
+ self._sim_ag_of_init(init_id)

self.ag_dict[ag_id]['init_ids'].extend([init_id])
return self.ag_dict[ag_id]
--
1.8.3.1
Gris Ge
2014-07-29 15:18:07 UTC
Permalink
* Sync with changes of access group capabilities.
* PEP8 clean up

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

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index c008d55..a863535 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -281,9 +281,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):
c.set(Capabilities.VOLUME_MASK)
c.set(Capabilities.VOLUME_UNMASK)
c.set(Capabilities.ACCESS_GROUPS)
- c.set(Capabilities.ACCESS_GROUP_CREATE)
+ c.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
c.set(Capabilities.ACCESS_GROUP_DELETE)
- c.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD)
+ c.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
c.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
c.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
c.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
@@ -743,7 +743,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
(access_group.name, access_group.id))

return self._request("list_hostgroup_members", "stmf",
- [access_group.name])
+ [access_group.name])

@handle_nstor_errors
def access_group_initiator_add(self, access_group, init_id, init_type,
--
1.8.3.1
Gris Ge
2014-07-29 15:18:09 UTC
Permalink
* Complete rewrite of capabilities() methods except volume replication
related.
* SMI-S profile based capabilities check.
# Assuming every vendor is following the SNIA standard mandatory settings.
# We are not SNIA CTP test, we can redirect bug to vendor if user
# complained.

* Tested on:
EMC VNX, EMC VMAX, Fujitsu Eternus, HDS AMS, Dell Compellent, HP 3par,
Dot Hill DHS

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

diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index bf2355d..3ad92f8 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -65,6 +65,11 @@ from lsm import (IStorageAreaNetwork, error, uri_parse, LsmError, ErrorNumber,
# _cim_xxx_of_id(some_id)
# Return CIMInstance for given ID

+# Terminology
+# SPC CIM_SCSIProtocolController
+# BSP SNIA SMI-S 'Block Services Package' profile
+# Group M&M SNIA SMI-S 'Group Masking and Mapping' profile
+

def handle_cim_errors(method):
def cim_wrapper(*args, **kwargs):
@@ -178,6 +183,11 @@ class DMTF(object):
# Allowing empty DeviceMaskingGroup associated to SPC
GMM_CAP_INIT_MG_ALLOW_EMPTY_W_SPC = pywbem.Uint16(5)

+ # CIM_GroupMaskingMappingCapabilities['SupportedAsynchronousActions']
+ # and 'SupportedSynchronousActions'. They are using the same value map.
+ GMM_CAP_DELETE_SPC = pywbem.Uint16(24)
+ GMM_CAP_DELETE_GROUP = pywbem.Uint16(20)
+
_INIT_TYPE_CONV = {
DMTF.ID_TYPE_OTHER: AccessGroup.INIT_TYPE_OTHER,
DMTF.ID_TYPE_WWPN: AccessGroup.INIT_TYPE_WWPN,
@@ -730,53 +740,72 @@ class Smis(IStorageAreaNetwork):
def plugin_unregister(self, flags=0):
self._c = None

- def _scs_supported_capabilities(self, system, cap):
+ def _bsp_cap_set(self, cim_sys_path, cap):
"""
- Interrogate the supported features of the Storage Configuration
- service
+ Set capabilities for these methods:
+ volumes()
+ volume_create()
+ volume_resize()
+ volume_delete()
"""
- scs = self._get_class_instance('CIM_StorageConfigurationService',
- 'SystemName', system.id)
-
- if scs is not None:
- scs_cap_inst = self._c.Associators(
- scs.path,
- AssocClass='CIM_ElementCapabilities',
- ResultClass='CIM_StorageConfigurationCapabilities')[0]
-
- if scs_cap_inst is not None:
- # print 'Async', scs_cap_inst['SupportedAsynchronousActions']
- # print 'Sync', scs_cap_inst['SupportedSynchronousActions']
- async = None
- sync = None
-
- if 'SupportedAsynchronousActions' in scs_cap_inst:
- async = scs_cap_inst['SupportedAsynchronousActions']
- if 'SupportedSynchronousActions' in scs_cap_inst:
- sync = scs_cap_inst['SupportedSynchronousActions']
-
- if async is None:
- async = []
+ if self.fallback_mode:
+ # pools() is mandatory, we will try pools() related methods first
+ try:
+ self._cim_pools_of(cim_sys_path)
+ except CIMError as e:
+ if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
+ e[0] == pywbem.CIM_ERR_INVALID_CLASS:
+ raise LsmError(ErrorNumber.NO_SUPPORT,
+ "Target SMI-S provider does not support "
+ "CIM_StoragePool querying which is "
+ "mandatory for pools() method")
+ else:
+ raise
+ # For fallback mode, if StoragePool is supported, then BSP is
+ # supported.
+ #
+ # For interop, plugin_register() already ensured the support
+ # of 1.4+ Array profile which make 'BSP' mandatory as sub profile.
+ # This is mandatory for BSP profile:
+ cap.set(Capabilities.VOLUMES)

- if sync is None:
- sync = []
+ # CIM_StorageConfigurationService is optional.
+ cim_scs_path = self._get_cim_service_path(
+ cim_sys_path, 'CIM_StorageConfigurationService')

- combined = async
- combined.extend(sync)
+ if cim_scs_path is None:
+ return

- #TODO Get rid of magic numbers
- if 'SupportedStorageElementTypes' in scs_cap_inst:
- if 2 in scs_cap_inst['SupportedStorageElementTypes']:
- cap.set(Capabilities.VOLUMES)
+ # These methods are mandatory for CIM_StorageConfigurationService:
+ # CreateOrModifyElementFromStoragePool()
+ # ReturnToStoragePool()
+ cap.set(Capabilities.VOLUME_CREATE)
+ cap.set(Capabilities.VOLUME_DELETE)
+ cap.set(Capabilities.VOLUME_RESIZE)

- if 5 in combined:
- cap.set(Capabilities.VOLUME_CREATE)
+ return

- if 6 in combined:
- cap.set(Capabilities.VOLUME_DELETE)
+ def _disk_cap_set(self, cim_sys_path, cap):
+ if self.fallback_mode:
+ try:
+ # Assuming provider support disk drive when systems under it
+ # support it.
+ self._enumerate('CIM_DiskDrive')
+ except CIMError as e:
+ if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
+ e[0] == pywbem.CIM_ERR_INVALID_CLASS:
+ return
+ else:
+ raise
+ else:
+ if not self._profile_is_supported(SNIA.DISK_LITE_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False):
+ return

- if 7 in combined:
- cap.set(Capabilities.VOLUME_RESIZE)
+ cap.set(Capabilities.DISKS)
+ return

def _rs_supported_capabilities(self, system, cap):
"""
@@ -827,7 +856,7 @@ class Smis(IStorageAreaNetwork):
if rs_cap is not None and 'SupportedCopyTypes' in rs_cap:
sct = rs_cap['SupportedCopyTypes']

- if len(sct):
+ if sct and len(sct):
cap.set(Capabilities.VOLUME_REPLICATE)

# Mirror support is not working and is not supported at
@@ -845,65 +874,60 @@ class Smis(IStorageAreaNetwork):
if Smis.CopyTypes.UNSYNCUNASSOC in sct:
cap.set(Capabilities.VOLUME_REPLICATE_COPY)

- def _pcm_supported_capabilities(self, system, cap):
- """
- Interrogate the supported features of
- CIM_ProtocolControllerMaskingCapabilities
+ def _mask_map_cap_set(self, cim_sys_path, cap):
"""
+ In SNIA SMI-S 1.4rev6 'Masking and Mapping' profile:
+ CIM_ControllerConfigurationService is mandatory
+ and it's ExposePaths() and HidePaths() are mandatory

- # Get the cim object that represents the system
- cim_sys = None
- cim_pcms = None
- cim_sys = self._get_cim_instance_by_id('System', system.id)
+ For fallback mode, once we found CIM_ControllerConfigurationService,
+ we assume they are supporting 1.4rev6 'Masking and Mapping' profile.
+ Fallback mode means target provider does not support interop, but
+ they still need to follow at least SNIA SMI-S 1.4rev6
+ """
if self.fallback_mode:
-
- # Using 'ExposePathsSupported of
- # CIM_ProtocolControllerMaskingCapabilities
- # to check support status of HidePaths() and ExposePaths() is
- # not documented by SNIA SMI-S 1.4 or 1.6, but only defined in
- # DMTF CIM MOF files.
- try:
- cim_pcms = self._c.Associators(
- cim_sys.path,
- ResultClass='CIM_ProtocolControllerMaskingCapabilities')
- except CIMError as e:
- if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
- e[0] == pywbem.CIM_ERR_INVALID_CLASS:
- return
- if cim_pcms is not None and len(cim_pcms) == 1:
- cap.set(Capabilities.ACCESS_GROUPS)
- cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
- cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
-
- if cim_pcms[0]['ExposePathsSupported']:
- cap.set(Capabilities.VOLUME_MASK)
- cap.set(Capabilities.VOLUME_UNMASK)
- cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD)
- cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
+ cim_ccs_path = self._get_cim_service_path(
+ cim_sys_path, 'CIM_ControllerConfigurationService')
+ if cim_ccs_path is None:
return
- else:
- # Since SNIA SMI-S 1.4rev6:
- # CIM_ControllerConfigurationService is mandatory
- # and it's ExposePaths() and HidePaths() are mandatory
- cap.set(Capabilities.ACCESS_GROUPS)
- cap.set(Capabilities.VOLUME_MASK)
- cap.set(Capabilities.VOLUME_UNMASK)
- cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD)
- cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
- cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
- cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
+
+ elif not self._profile_is_supported(SNIA.MASK_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False):
+ return
+
+ cap.set(Capabilities.ACCESS_GROUPS)
+ cap.set(Capabilities.VOLUME_MASK)
+ cap.set(Capabilities.VOLUME_UNMASK)
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
+ cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
+ cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
+
+ # EMC VNX does not support CreateStorageHardwareID for iSCSI
+ # and require WWNN for WWPN. Hence both are not supported.
+ if cim_sys_path.classname == 'Clar_StorageSystem':
+ return
+
+ if self._fc_tgt_is_supported(cim_sys_path):
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_WWPN)
+ if self._iscsi_tgt_is_supported(cim_sys_path):
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
+ return

def _common_capabilities(self, system):
cap = Capabilities()

- # Assume that the SMI-S we are talking to supports blocks
- cap.set(Capabilities.BLOCK_SUPPORT)
-
- self._scs_supported_capabilities(system, cap)
self._rs_supported_capabilities(system, cap)
return cap

- def _tgt_port_capabilities(self, system, cap):
+ def _tgt_cap_set(self, cim_sys_path, cap):
+
+ # LSI MegaRAID actually not support FC Target and iSCSI target,
+ # They expose empty list of CIM_FCPort
+ if cim_sys_path.classname == 'LSIESG_MegaRAIDHBA':
+ return
+
flag_fc_support = False
flag_iscsi_support = False
if self.fallback_mode:
@@ -911,18 +935,14 @@ class Smis(IStorageAreaNetwork):
flag_iscsi_support = True
# CIM_FCPort is the contral class of FC Targets profile
try:
- self._enumerate('CIM_FCPort')
+ self._cim_fc_tgt_of(cim_sys_path)
except CIMError as e:
if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
e[0] == pywbem.CIM_ERR_INVALID_CLASS:
flag_fc_support = False

- # Even CIM_EthernetPort is the contral class of iSCSI Target
- # Ports profile, but that class is optional. :(
- # We use CIM_iSCSIProtocolEndpoint as it's a start point we are
- # using in our code of target_ports().
try:
- self._enumerate('CIM_iSCSIProtocolEndpoint')
+ self._cim_iscsi_pg_of(cim_sys_path)
except CIMError as e:
if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
e[0] == pywbem.CIM_ERR_INVALID_CLASS:
@@ -953,11 +973,100 @@ class Smis(IStorageAreaNetwork):
cap.set(Capabilities.TARGET_PORTS)
return

+ def _group_mask_map_cap_set(self, cim_sys_path, cap):
+ """
+ We set caps for these methods recording to 1.5+ Group M&M profile:
+ access_groups()
+ access_groups_granted_to_volume()
+ volumes_accessible_by_access_group()
+ access_group_initiator_add()
+ access_group_initiator_delete()
+ volume_mask()
+ volume_unmask()
+ access_group_create()
+ access_group_delete()
+ """
+ # These are mandatory in SNIA SMI-S.
+ # We are not in the position of SNIA SMI-S certification.
+ cap.set(Capabilities.ACCESS_GROUPS)
+ cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
+ cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
+ cap.set(Capabilities.VOLUME_MASK)
+ if self._fc_tgt_is_supported(cim_sys_path):
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_WWPN)
+ cap.set(Capabilities.ACCESS_GROUP_CREATE_WWPN)
+ if self._iscsi_tgt_is_supported(cim_sys_path):
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
+ cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
+
+ # RemoveMembers is also mandatory, but we require target provider
+ # to support empty InitiatorMaskingGroup.
+ cim_gmm_cap_pros = [
+ 'SupportedInitiatorGroupFeatures',
+ 'SupportedAsynchronousActions',
+ 'SupportedSynchronousActions',
+ 'SupportedDeviceGroupFeatures']
+
+ cim_gmm_cap = self._c.Associators(
+ cim_sys_path,
+ AssocClass='CIM_ElementCapabilities',
+ ResultClass='CIM_GroupMaskingMappingCapabilities',
+ PropertyList=cim_gmm_cap_pros)[0]
+
+ if DMTF.GMM_CAP_INIT_MG_ALLOW_EMPTY in \
+ cim_gmm_cap['SupportedInitiatorGroupFeatures']:
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
+
+ # if empty dev group in spc is allowed, RemoveMembers() is enough
+ # to do volume_unamsk(). RemoveMembers() is mandatory.
+ if DMTF.GMM_CAP_DEV_MG_ALLOW_EMPTY_W_SPC in \
+ cim_gmm_cap['SupportedDeviceGroupFeatures']:
+ cap.set(Capabilities.VOLUME_UNMASK)
+
+ # DeleteMaskingView() is optional, this is required by volume_unmask()
+ # when empty dev group in spc not allowed.
+ elif ((DMTF.GMM_CAP_DELETE_SPC in
+ cim_gmm_cap['SupportedSynchronousActions']) or
+ (DMTF.GMM_CAP_DELETE_SPC in
+ cim_gmm_cap['SupportedAsynchronousActions'])):
+ cap.set(Capabilities.VOLUME_UNMASK)
+
+ # DeleteGroup is optional, this is required by access_group_delete()
+ if ((DMTF.GMM_CAP_DELETE_GROUP in
+ cim_gmm_cap['SupportedSynchronousActions']) or
+ (DMTF.GMM_CAP_DELETE_GROUP in
+ cim_gmm_cap['SupportedAsynchronousActions'])):
+ cap.set(Capabilities.ACCESS_GROUP_DELETE)
+ return None
+
@handle_cim_errors
def capabilities(self, system, flags=0):
- cap = self._common_capabilities(system)
- self._pcm_supported_capabilities(system, cap)
- self._tgt_port_capabilities(system, cap)
+
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', system.id, raise_error=True)
+
+ cap = Capabilities()
+
+ # 'Block Services Package' profile
+ self._bsp_cap_set(cim_sys.path, cap)
+
+ # 'Disk Drive Lite' profile
+ self._disk_cap_set(cim_sys.path, cap)
+
+ # 'Masking and Mapping' and 'Group Masking and Mapping' profiles
+ mask_type = self._mask_type()
+ if cim_sys.path.classname == 'Clar_StorageSystem':
+ mask_type = Smis.MASK_TYPE_MASK
+
+ if mask_type == Smis.MASK_TYPE_GROUP:
+ self._group_mask_map_cap_set(cim_sys.path, cap)
+ else:
+ self._mask_map_cap_set(cim_sys.path, cap)
+
+ # 'FC Target Ports' and 'iSCSI Target Ports' profiles
+ self._tgt_cap_set(cim_sys.path, cap)
+
+ self._rs_supported_capabilities(system, cap)
return cap

@handle_cim_errors
@@ -2250,7 +2359,9 @@ class Smis(IStorageAreaNetwork):
cim_sys.path,
AssocClass='CIM_ElementCapabilities',
ResultClass='CIM_GroupMaskingMappingCapabilities',
- PropertyList=['SupportedDeviceGroupFeatures'])[0]
+ PropertyList=['SupportedDeviceGroupFeatures',
+ 'SupportedSynchronousActions',
+ 'SupportedAsynchronousActions'])[0]

flag_empty_dev_in_spc = False

@@ -2258,6 +2369,18 @@ class Smis(IStorageAreaNetwork):
cim_gmm_cap['SupportedDeviceGroupFeatures']:
flag_empty_dev_in_spc = True

+ if flag_empty_dev_in_spc is False:
+ if ((DMTF.GMM_CAP_DELETE_SPC not in
+ cim_gmm_cap['SupportedSynchronousActions']) and
+ (DMTF.GMM_CAP_DELETE_SPC not in
+ cim_gmm_cap['SupportedAsynchronousActions'])):
+ raise LsmError(
+ ErrorNumber.NO_SUPPORT,
+ "volume_unmask() not supported. It requires one of these "
+ "1. support of DeleteMaskingView(). 2. allowing empty "
+ "DeviceMaskingGroup in SPC. But target SMI-S provider "
+ "does not support any of these")
+
cim_gmm_path = self._get_cim_service_path(
cim_sys.path, 'CIM_GroupMaskingMappingService')

@@ -2419,7 +2542,7 @@ class Smis(IStorageAreaNetwork):
except CIMError as ce:
error_code = tuple(ce)[0]
if error_code == pywbem.CIM_ERR_INVALID_CLASS or \
- error_code == pywbem.CIM_ERR_INVALID_PARAMETER:
+ error_code == pywbem.CIM_ERR_INVALID_PARAMETER:
raise LsmError(ErrorNumber.NO_SUPPORT,
'AccessGroup is not supported ' +
'by this array')
@@ -2829,7 +2952,8 @@ class Smis(IStorageAreaNetwork):
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.")
+ "Please do it via EMC vendor specific tools. "
+ "EMC VNX does not support adding iSCSI IQN neither")

cim_spc = self._cim_spc_of_id(access_group.id, raise_error=True)

@@ -4120,14 +4244,20 @@ class Smis(IStorageAreaNetwork):
else:
return cim_syss

- def _fc_tgt_is_supported(self):
+ def _fc_tgt_is_supported(self, cim_sys_path):
"""
Return True if FC Target Port 1.4+ profile is supported.
- For fallback_mode, always return True.
- Return False else.
+ For fallback_mode, we call self._cim_fc_tgt_of() and do try-except
"""
if self.fallback_mode:
+ try:
+ self._cim_fc_tgt_of(cim_sys_path)
+ except CIMError as e:
+ if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
+ e[0] == pywbem.CIM_ERR_INVALID_CLASS:
+ return False
return True
+
flag_fc_support = self._profile_is_supported(
SNIA.FC_TGT_PORT_PROFILE,
SNIA.SMIS_SPEC_VER_1_4,
@@ -4148,23 +4278,31 @@ class Smis(IStorageAreaNetwork):
else:
return False

- def _iscsi_tgt_is_supported(self):
+ def _iscsi_tgt_is_supported(self, cim_sys_path):
"""
Return True if FC Target Port 1.4+ profile is supported.
- For fallback_mode, always return True.
- Return False else.
+ For fallback_mode, we call self._cim_iscsi_pg_of() and do try-except
+ For fallback_mode:
+ Even CIM_EthernetPort is the contral class of iSCSI Target
+ Ports profile, but that class is optional. :(
+ We use CIM_iSCSIProtocolEndpoint as it's a start point we are
+ using in our code of target_ports().
"""
if self.fallback_mode:
+ try:
+ self._cim_iscsi_pg_of(cim_sys_path)
+ except CIMError as e:
+ if e[0] == pywbem.CIM_ERR_NOT_SUPPORTED or \
+ e[0] == pywbem.CIM_ERR_INVALID_CLASS:
+ return False
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:
+
+ if self._profile_is_supported(SNIA.ISCSI_TGT_PORT_PROFILE,
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False,
+ raise_error=False):
return True
- else:
- return False
+ return False

def _multi_sys_is_supported(self):
"""
@@ -4466,17 +4604,6 @@ class Smis(IStorageAreaNetwork):
@handle_cim_errors
def target_ports(self, search_key=None, search_value=None, flags=0):
rc = []
- flag_fc_support = self._fc_tgt_is_supported()
- flag_iscsi_support = self._iscsi_tgt_is_supported()
-
- 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',
@@ -4486,6 +4613,21 @@ class Smis(IStorageAreaNetwork):
property_list=self._property_list_of_id('System'))
for cim_sys in cim_syss:
system_id = self._sys_id(cim_sys)
+ flag_fc_support = self._fc_tgt_is_supported(cim_sys.path)
+ flag_iscsi_support = self._iscsi_tgt_is_supported(cim_sys.path)
+
+ # Assuming: if one system does not support target_ports(),
+ # all systems from the same provider will not support
+ # target_ports().
+ 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))
+
if flag_fc_support:
# CIM_FCPort might be not belong to root cim_sys
# In that case, CIM_FCPort['SystemName'] will not be
@@ -4564,7 +4706,7 @@ class Smis(IStorageAreaNetwork):
cim_job_path = out['Job']
loop_counter = 0
job_pros = ['JobState', 'PercentComplete', 'ErrorDescription',
- 'OperationalStatus']
+ 'OperationalStatus']
cim_xxxs_path = []
while(loop_counter <= Smis._INVOKE_MAX_LOOP_COUNT):
cim_job = self._c.GetInstance(cim_job_path,
@@ -4572,7 +4714,7 @@ class Smis(IStorageAreaNetwork):
LocalOnly=False)
job_state = cim_job['JobState']
if job_state in (Smis.JS_NEW, Smis.JS_STARTING,
- Smis.JS_RUNNING):
+ Smis.JS_RUNNING):
loop_counter += 1
time.sleep(Smis._INVOKE_CHECK_INTERVAL)
continue
@@ -4696,8 +4838,18 @@ class Smis(IStorageAreaNetwork):
"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()
+ cim_sys = self._get_cim_instance_by_id(
+ 'System', system.id, raise_error=True)
+ 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")
+
+ flag_fc_support = self._fc_tgt_is_supported(cim_sys.path)
+ flag_iscsi_support = self._iscsi_tgt_is_supported(cim_sys.path)

if init_type == AccessGroup.INIT_TYPE_WWPN and not flag_fc_support:
raise LsmError(ErrorNumber.NO_SUPPORT,
@@ -4712,15 +4864,6 @@ class Smis(IStorageAreaNetwork):
"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)
--
1.8.3.1
Gris Ge
2014-07-29 15:18:10 UTC
Permalink
* Rename FS_SNAPSHOT_REVERT to FS_SNAPSHOT_RESTORE in nstor and ontap plugin.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/nstor/nstor.py | 4 ++--
plugin/ontap/ontap.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index a863535..22e053d 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -250,8 +250,8 @@ class NexentaStor(INfs, IStorageAreaNetwork):
c.set(Capabilities.FS_SNAPSHOTS)
c.set(Capabilities.FS_SNAPSHOT_CREATE)
c.set(Capabilities.FS_SNAPSHOT_DELETE)
- c.set(Capabilities.FS_SNAPSHOT_REVERT)
- # c.set(Capabilities.FS_SNAPSHOT_REVERT_SPECIFIC_FILES)
+ c.set(Capabilities.FS_SNAPSHOT_RESTORE)
+ # c.set(Capabilities.FS_SNAPSHOT_RESTORE_SPECIFIC_FILES)
c.set(Capabilities.FS_CHILD_DEPENDENCY)
c.set(Capabilities.FS_CHILD_DEPENDENCY_RM)
# c.set(Capabilities.FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES)
diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index fb0244d..1662d4d 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -437,7 +437,7 @@ class Ontap(IStorageAreaNetwork, INfs):
cap.set(Capabilities.FS_SNAPSHOTS)
cap.set(Capabilities.FS_SNAPSHOT_CREATE)
cap.set(Capabilities.FS_SNAPSHOT_DELETE)
- cap.set(Capabilities.FS_SNAPSHOT_REVERT)
+ cap.set(Capabilities.FS_SNAPSHOT_RESTORE)
cap.set(Capabilities.FS_CHILD_DEPENDENCY)
cap.set(Capabilities.FS_CHILD_DEPENDENCY_RM)
cap.set(Capabilities.EXPORT_AUTH)
--
1.8.3.1
Tony Asleson
2014-07-29 21:27:25 UTC
Permalink
Comments below, see updated patch set for proposed code changes and a
few fixes.

Thanks,
Tony
Post by Gris Ge
BLOCK_SUPPORT
FS_SUPPORT
# We don't actually need them.
This is fine as users can use specific capabilities.
Post by Gris Ge
ACCESS_GROUP_CREATE -> ACCESS_GROUP_CREATE_WWPN
ACCESS_GROUP_INITIATOR_ADD -> ACCESS_GROUP_INITIATOR_ADD_WWPN
FS_SNAPSHOT_REVERT -> FS_SNAPSHOT_RESTORE
FS_SNAPSHOT_REVERT_SPECIFIC_FILES -> FS_SNAPSHOT_RESTORE_SPECIFIC_FILES
# To match the name of lsm.Client.fs_snapshot_restore()
ACCESS_GROUP_CREATE_ISCSI_IQN
ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN
ACCESS_GROUP_INITIATOR_ADD_MIX
# _ADD_MIX means can add different type of init_id into access group.
# _ADD_ISCSI_IQN and _ADD_WWPN indicate the types supported.
# Useful when access group is empty.
In a previous patch you mentioned we should prevent the deletion of the
last initiator in a group, thus preventing an access group from being
empty. So I'm confused by this comment. Did you intend to say "Useful
when creating a new access group." ?
Post by Gris Ge
lsm.Capabilities.get_supported()
# Return a list of integer with supported capabilities.
lsm.Capabilities.valid_capabilities()
# Return a list of integer with all valid capabilities(support or not
# support).
lsm.Capabilities.lsm_cap_to_str()
# Convert LSM capability integer into string.
# This save the coding workload of lsmcli.
All of these methods seem to be tailored to one purpose, making the CLI
implementation easier. However, supporting these methods in C will not
be trivial or potentially even that useful. C users can't use if foo in
foo_list syntax. I'm thinking that for this we should for the python
API include a method that returns a hash, with an optional parameter
'all' that can default to False. This way the cli in one call can get
the hash map and then if needed inverse that hash map by doing:

inv_cap = { v:k for k, v in cap_hash.items() }

Then the CLI can do whatever it needs to do dump output to the
screen/script.
Post by Gris Ge
---
python_binding/lsm/_data.py | 73 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 64 insertions(+), 9 deletions(-)
diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index 8fd42a3..38d91dc 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
SUPPORTED # Supported
) = (0, 1)
- _NUM = 512
+ _NUM = 512 # Indicate the maximum capability integer
- #Array wide
- BLOCK_SUPPORT = 0 # Array handles block operations
- FS_SUPPORT = 1 # Array handles file system
+ _CAP_NUM_BEGIN = 20 # Indicate the first capability integer
#Block operations
VOLUMES = 20
VOLUME_MASK = 36
VOLUME_UNMASK = 37
ACCESS_GROUPS = 38
- ACCESS_GROUP_CREATE = 39
+ ACCESS_GROUP_CREATE_WWPN = 39
ACCESS_GROUP_DELETE = 40
- ACCESS_GROUP_INITIATOR_ADD = 41
+ ACCESS_GROUP_INITIATOR_ADD_WWPN = 41
+ # For empty access group, this indicate it can add WWPN into it.
ACCESS_GROUP_INITIATOR_DELETE = 42
VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46
+ ACCESS_GROUP_CREATE_ISCSI_IQN = 47
+ ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN = 48
+ # For empty access group, this indicate it can add iSCSI IQN into it.
+
+ ACCESS_GROUP_INITIATOR_ADD_MIX = 49
+ # Allowing add different initiator type than existing one
+
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53
VOLUME_THIN = 55
FS_SNAPSHOTS = 106
FS_SNAPSHOT_CREATE = 107
FS_SNAPSHOT_DELETE = 109
- FS_SNAPSHOT_REVERT = 110
- FS_SNAPSHOT_REVERT_SPECIFIC_FILES = 111
+ FS_SNAPSHOT_RESTORE = 110
+ FS_SNAPSHOT_RESTORE_SPECIFIC_FILES = 111
FS_CHILD_DEPENDENCY = 112
FS_CHILD_DEPENDENCY_RM = 113
FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES = 114
ACCESS_GROUPS_QUICK_SEARCH = 213
FS_QUICK_SEARCH = 214
NFS_EXPORTS_QUICK_SEARCH = 215
- TARGET_PORTS = 215
+ TARGET_PORTS = 216
TARGET_PORTS_QUICK_SEARCH = 217
+ DISKS = 220
+
rc = {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
return Capabilities.UNSUPPORTED
return self._cap[capability]
+ """
+ integer => string name
+ """
+ lsm_cap_to_str_conv = dict()
+ if type(cap_str) == str and \
+ type(cap_int) == int and \
+ cap_str[0] != '_' and \
+ cap_int >= Capabilities._CAP_NUM_BEGIN and \
+ lsm_cap_to_str_conv[cap_int] = cap_str
+ return lsm_cap_to_str_conv
+
+ """
+ Return a list of integer containing only the supported capabilities
+ """
+ rc = []
+ lsm_cap_to_str_conv = Capabilities._lsm_cap_to_str_dict()
+ rc.append(cap_int)
+ return sorted(rc)
+
+ """
+ Retrun a list of integer containing valid capabilities.
+ """
+ lsm_cap_to_str_conv = Capabilities._lsm_cap_to_str_dict()
+ return sorted(lsm_cap_to_str_conv.keys())
+
+ """
+ Convert capability integer to string(constant name)
+ Return None if lsm_cap is not a valid capability.
+ """
+ lsm_cap_to_str_conv = Capabilities._lsm_cap_to_str_dict()
+ return lsm_cap_to_str_conv[lsm_cap]
+ return None
+
self._cap[capability] = value
return None
Gris Ge
2014-07-30 02:16:04 UTC
Permalink
Post by Tony Asleson
In a previous patch you mentioned we should prevent the deletion of the
last initiator in a group, thus preventing an access group from being
empty. So I'm confused by this comment. Did you intend to say "Useful
when creating a new access group." ?
We only refuse the removal of last initiator of *masked* access group.
Supporting 'empty' access group is mandatory.
Post by Tony Asleson
Post by Gris Ge
lsm.Capabilities.get_supported()
# Return a list of integer with supported capabilities.
lsm.Capabilities.valid_capabilities()
# Return a list of integer with all valid capabilities(support or not
# support).
lsm.Capabilities.lsm_cap_to_str()
# Convert LSM capability integer into string.
# This save the coding workload of lsmcli.
All of these methods seem to be tailored to one purpose, making the CLI
implementation easier. However, supporting these methods in C will not
Yes. It makes CLI easier.
Post by Tony Asleson
be trivial or potentially even that useful. C users can't use if foo in
foo_list syntax. I'm thinking that for this we should for the python
API include a method that returns a hash, with an optional parameter
'all' that can default to False. This way the cli in one call can get
inv_cap = { v:k for k, v in cap_hash.items() }
Then the CLI can do whatever it needs to do dump output to the
screen/script.
Just one concern. Python dict is not ordered. If use OrderedDict, we
need to change RPC serialize code.

I will try implement the C part and find out a better way.

Thanks for the comments.
Best regards.
--
Gris Ge
Gris Ge
2014-07-30 06:33:38 UTC
Permalink
Post by Gris Ge
Post by Tony Asleson
Then the CLI can do whatever it needs to do dump output to the
screen/script.
Just one concern. Python dict is not ordered. If use OrderedDict, we
need to change RPC serialize code.
Your implementation in 11/11 patch set is better than mine.
Post by Gris Ge
I will try implement the C part and find out a better way.
Thanks for the comments.
Best regards.
--
Gris Ge
--
Gris Ge
Loading...