Discussion:
[Libstoragemgmt-devel] [PATCH 00/10] Capabilities revisions
Tony Asleson
2014-07-29 21:25:54 UTC
Permalink
This is Gris's patch set with modifictions to
1st and 3rd patches. I simplified by just exposing
one method that can be used to retrieve the supported
methods as a dict or all methods as a dict.

The last 2 patches are additions that fix up a couple of
small issues I found in patch series.


I also renamed the patch for one of the pathes named:

"ontap plugin: fix changed capacities" as one of them
was actually for nstor.

Please review.

Gris Ge (8):
python library: lsm.Capacities changes (V2)
test: fix changed capabilities
lsmcli: improve capabilities code V2
ontap plugin: fix changed capacities
nstor plugin: fix changed capacities
sim plugin: more strict access group related methods
smis plugin: lsm.Client.capabilities() rewrite
nstor and ontap plugin: change FS_SNAPSHOT_REVERT to
FS_SNAPSHOT_RESTORE

Tony Asleson (2):
nstor.py: Remove references to removed constants.
ontap.py: Remove references to removed constants

plugin/nstor/nstor.py | 14 +-
plugin/ontap/ontap.py | 11 +-
plugin/sim/simarray.py | 44 ++++-
plugin/smispy/smis.py | 419 +++++++++++++++++++++++++++++--------------
python_binding/lsm/_data.py | 40 ++++-
test/cmdtest.py | 22 +--
test/plugin_test.py | 89 +++++----
tools/lsmcli/cmdline.py | 118 +++---------
tools/lsmcli/data_display.py | 4 +-
9 files changed, 450 insertions(+), 311 deletions(-)
--
1.8.2.1
Tony Asleson
2014-07-29 21:25:55 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.

* Added public methods:
get_supported: Returns a hash of supported capabilities

V2: tasleson, changed public methods added

Signed-off-by: Gris Ge <***@redhat.com>
Signed-off-by: Tony Asleson <***@redhat.com>
---
python_binding/lsm/_data.py | 39 +++++++++++++++++++++++++++++++++------
1 file changed, 33 insertions(+), 6 deletions(-)

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

- _NUM = 512
-
- #Array wide
- BLOCK_SUPPORT = 0 # Array handles block operations
- FS_SUPPORT = 1 # Array handles file system
+ _CAP_NUM_BEGIN = 10 # Beginning capability
+ _NUM = 512 # Indicate the maximum capability integer

#Block operations
VOLUMES = 20
@@ -860,10 +857,40 @@ class Capabilities(IData):
return False

def get(self, capability):
- if capability > len(self._cap):
+ if capability >= len(self._cap):
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 \
+ Capabilities._CAP_NUM_BEGIN <= cap_int <= Capabilities._NUM:
+ lsm_cap_to_str_conv[cap_int] = cap_str
+ return lsm_cap_to_str_conv
+
+ def get_supported(self, all_cap=False):
+ """
+ Returns a hash of the supported capabilities in the form
+ constant, name
+ """
+ all_caps = Capabilities._lsm_cap_to_str_dict()
+
+ if all_cap:
+ return all_caps
+
+ rc = {}
+ for i in range(0, len(self._cap)):
+ if self._cap[i] == Capabilities.SUPPORTED:
+ rc[i] = all_caps[i]
+ return rc
+
def set(self, capability, value=SUPPORTED):
self._cap[capability] = value
return None
--
1.8.2.1
Tony Asleson
2014-07-29 21:25:56 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.2.1
Tony Asleson
2014-07-29 21:25:59 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.2.1
Tony Asleson
2014-07-29 21:25:57 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.

V2: Used the only one newly available public method

Signed-off-by: Gris Ge <***@redhat.com>
Signed-off-by: Tony Asleson <***@redhat.com>
---
python_binding/lsm/_data.py | 5 +-
tools/lsmcli/cmdline.py | 118 ++++++++-----------------------------------
tools/lsmcli/data_display.py | 4 +-
3 files changed, 25 insertions(+), 102 deletions(-)

diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index 5c8a299..3bf0049 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -872,7 +872,7 @@ class Capabilities(IData):
if type(cap_str) == str and type(cap_int) == int and \
cap_str[0] != '_' and \
Capabilities._CAP_NUM_BEGIN <= cap_int <= Capabilities._NUM:
- lsm_cap_to_str_conv[cap_int] = cap_str
+ lsm_cap_to_str_conv[int(cap_int)] = cap_str
return lsm_cap_to_str_conv

def get_supported(self, all_cap=False):
@@ -888,7 +888,8 @@ class Capabilities(IData):
rc = {}
for i in range(0, len(self._cap)):
if self._cap[i] == Capabilities.SUPPORTED:
- rc[i] = all_caps[i]
+ if i in all_caps:
+ rc[i] = all_caps[i]
return rc

def set(self, capability, value=SUPPORTED):
diff --git a/tools/lsmcli/cmdline.py b/tools/lsmcli/cmdline.py
index 0af2034..4909ae8 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,23 @@ 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 = sorted(cap.get_supported().values())
+ all_caps = sorted(cap.get_supported(True).values())
+
+ sep = DisplayData.DEFAULT_SPLITTER
+ if self.args.sep is not None:
+ sep = self.args.sep
+
+ cap_data = OrderedDict()
+ # Show support capabilities first
+ for v in sup_caps:
+ cap_data[v] = 'SUPPORTED'
+
+ for v in all_caps:
+ if v not in sup_caps:
+ cap_data[v] = '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.2.1
Tony Asleson
2014-07-29 21:26:02 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.2.1
Tony Asleson
2014-07-29 21:26:01 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.2.1
Tony Asleson
2014-07-29 21:26:03 UTC
Permalink
Signed-off-by: Tony Asleson <***@redhat.com>
---
plugin/nstor/nstor.py | 4 ----
1 file changed, 4 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index 22e053d..c29c712 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -236,10 +236,6 @@ class NexentaStor(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
c = Capabilities()

- #Array wide
- # c.set(Capabilities.BLOCK_SUPPORT)
- c.set(Capabilities.FS_SUPPORT)
-
#File system
c.set(Capabilities.FS)
c.set(Capabilities.FS_DELETE)
--
1.8.2.1
Tony Asleson
2014-07-29 21:25:58 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.2.1
Tony Asleson
2014-07-29 21:26:00 UTC
Permalink
From: Gris Ge <***@redhat.com>

* 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.2.1
Tony Asleson
2014-07-29 21:26:04 UTC
Permalink
Also corrected correct constant for
ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN

Signed-off-by: Tony Asleson <***@redhat.com>
---
plugin/ontap/ontap.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index 1662d4d..d33e230 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -401,8 +401,6 @@ class Ontap(IStorageAreaNetwork, INfs):
@handle_ontap_errors
def capabilities(self, system, flags=0):
cap = Capabilities()
- cap.set(Capabilities.BLOCK_SUPPORT)
- cap.set(Capabilities.FS_SUPPORT)
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.VOLUME_CREATE)
cap.set(Capabilities.VOLUME_RESIZE)
@@ -422,7 +420,7 @@ class Ontap(IStorageAreaNetwork, INfs):
cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
cap.set(Capabilities.ACCESS_GROUP_DELETE)
cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_WWPN)
- cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_IQN)
+ cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
--
1.8.2.1
Tony Asleson
2014-07-29 22:15:22 UTC
Permalink
Ah, I messed up on this patch set. The first re-worked patch I lost a
number of things from Gris's patch we need to keep.

Disregard this patch set, will send a new one.

-Tony
Post by Tony Asleson
This is Gris's patch set with modifictions to
1st and 3rd patches. I simplified by just exposing
one method that can be used to retrieve the supported
methods as a dict or all methods as a dict.
The last 2 patches are additions that fix up a couple of
small issues I found in patch series.
"ontap plugin: fix changed capacities" as one of them
was actually for nstor.
Please review.
python library: lsm.Capacities changes (V2)
test: fix changed capabilities
lsmcli: improve capabilities code V2
ontap plugin: fix changed capacities
nstor plugin: fix changed capacities
sim plugin: more strict access group related methods
smis plugin: lsm.Client.capabilities() rewrite
nstor and ontap plugin: change FS_SNAPSHOT_REVERT to
FS_SNAPSHOT_RESTORE
nstor.py: Remove references to removed constants.
ontap.py: Remove references to removed constants
plugin/nstor/nstor.py | 14 +-
plugin/ontap/ontap.py | 11 +-
plugin/sim/simarray.py | 44 ++++-
plugin/smispy/smis.py | 419 +++++++++++++++++++++++++++++--------------
python_binding/lsm/_data.py | 40 ++++-
test/cmdtest.py | 22 +--
test/plugin_test.py | 89 +++++----
tools/lsmcli/cmdline.py | 118 +++---------
tools/lsmcli/data_display.py | 4 +-
9 files changed, 450 insertions(+), 311 deletions(-)
Loading...