Discussion:
[Libstoragemgmt-devel] [PATCH 0/9] Query filter of lsm.Client.systems()
Gris Ge
2014-05-23 15:30:37 UTC
Permalink
* Introduce filter support of lsm.Client.systems():
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
* "python_binding/lsm_plugin_helper/" provides general system filter helper
for plugins, sample codes:
====
from lsm_plugin_helper import system_search()
if search_key:
return system_search(search_key, search_value, flags)
====
* Add new capability: lsm.Capabilities.SYSTEMS_QUICK_SEARCH
* Add new constant(list of string): lsm.System.SUPPORTED_SEARCH_KEYS
* New command line option:
lsmcli list --type systems --sys <SYS_ID>
* Tests for lsmcli about system query filter.
* There are duplicate codes for converting search_key from 'system_id' into
'id'. It was caused by allowing plugin running standalone.

Gris Ge (9):
Introduce new module: lsm_plugin_helper
lsm_plugin_helper: Update Makefile for new module
lsm_plugin_helper: Update RPM spec file for new module
python lsm library: Add filter support to lsm.Client.systems() method
python lsm library: Add ErrorNumer.UNSUPPORTED_SEARCH_KEY
python lsm library: Update lsm.System and lsm.Capabilities for filter
support
lsmcli: Introduce query filter support of system
lsm plugins: Use lsm_plugin_helper to support query filter of system
lsm test: Add system query filter test

doc/man/lsmcli.1.in | 4 +++
packaging/libstoragemgmt.spec.in | 2 ++
plugin/nstor/nstor.py | 6 +++-
plugin/ontap/ontap.py | 5 ++-
plugin/sim/simulator.py | 8 ++++-
plugin/smispy/smis.py | 5 ++-
plugin/targetd/targetd.py | 5 ++-
plugin/v7k/ibmv7k.py | 5 ++-
python_binding/Makefile.am | 6 ++++
python_binding/lsm/_client.py | 7 +++-
python_binding/lsm/_common.py | 2 ++
python_binding/lsm/_data.py | 3 ++
python_binding/lsm_plugin_helper/__init__.py | 18 ++++++++++
python_binding/lsm_plugin_helper/plugin_helper.py | 41 +++++++++++++++++++++++
test/cmdtest.py | 7 ++++
tools/lsmcli/cmdline.py | 11 +++++-
16 files changed, 127 insertions(+), 8 deletions(-)
create mode 100644 python_binding/lsm_plugin_helper/__init__.py
create mode 100644 python_binding/lsm_plugin_helper/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-05-23 15:30:38 UTC
Permalink
* "python_binding/lsm_plugin_helper/" provides general system filter helper
for plugins, sample codes:
====
from lsm_plugin_helper import system_search()
if search_key:
return system_search(search_key, search_value, flags)
====

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm_plugin_helper/__init__.py | 18 ++++++++++
python_binding/lsm_plugin_helper/plugin_helper.py | 41 +++++++++++++++++++++++
2 files changed, 59 insertions(+)
create mode 100644 python_binding/lsm_plugin_helper/__init__.py
create mode 100644 python_binding/lsm_plugin_helper/plugin_helper.py

diff --git a/python_binding/lsm_plugin_helper/__init__.py b/python_binding/lsm_plugin_helper/__init__.py
new file mode 100644
index 0000000..15ba643
--- /dev/null
+++ b/python_binding/lsm_plugin_helper/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from plugin_helper import system_search
diff --git a/python_binding/lsm_plugin_helper/plugin_helper.py b/python_binding/lsm_plugin_helper/plugin_helper.py
new file mode 100644
index 0000000..6221835
--- /dev/null
+++ b/python_binding/lsm_plugin_helper/plugin_helper.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from lsm import LsmError, ErrorNumber, System
+
+def _search_property(lsm_objs, search_key, search_value):
+ """
+ This method does not check whether lsm_obj contain requested property.
+ The method caller should do the check.
+ """
+ return list(lsm_obj for lsm_obj in lsm_objs
+ if getattr(lsm_obj, search_key) == search_value)
+
+def system_search(plugin_self, search_key, search_value, flags=0):
+ # These duplicate codes is for running plugin in standalone mode.
+ if search_key and search_key == 'system_id':
+ search_key = 'id'
+
+ # _client.py already checked the search_key.
+ # When running in plugin standalone, we might raise incorrect error,
+ # but that's acceptable considering standalone mode is only for plugin
+ # developer.
+ lsm_syss = plugin_self.systems(
+ search_key=None, search_value=None, flags=flags)
+ # currently, all system search keys are for property check.
+ return _search_property(lsm_syss, search_key, search_value)
+
--
1.8.3.1
Gris Ge
2014-05-23 15:30:40 UTC
Permalink
* Update RPM spec file for saving 'lsm_plugin_helper' module files into
'libstoragemgmt-python' package.

Signed-off-by: Gris Ge <***@redhat.com>
---
packaging/libstoragemgmt.spec.in | 2 ++
1 file changed, 2 insertions(+)

diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index b7b391a..58cacb5 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -335,6 +335,8 @@ fi
%{python_sitelib}/lsm/plugin/sim/__init__.*
%{python_sitelib}/lsm/plugin/sim/simulator.*
%{python_sitelib}/lsm/plugin/sim/simarray.*
+%{python_sitelib}/lsm_plugin_helper/__init__.py*
+%{python_sitelib}/lsm_plugin_helper/plugin_helper.py*
%{_bindir}/sim_lsmplugin

%files smis-plugin
--
1.8.3.1
Gris Ge
2014-05-23 15:30:39 UTC
Permalink
* Makefile update for saving 'lsm_plugin_helper' module into this folder:
$(pythondir)/lsm_plugin_helper

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/Makefile.am | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/python_binding/Makefile.am b/python_binding/Makefile.am
index 8b0581a..158ec03 100644
--- a/python_binding/Makefile.am
+++ b/python_binding/Makefile.am
@@ -15,3 +15,9 @@ lsm_PYTHON = \
external_PYTHON = \
lsm/external/__init__.py \
lsm/external/xmltodict.py
+
+lsm_plugin_helperdir= $(pythondir)/lsm_plugin_helper
+
+lsm_plugin_helper_PYTHON = \
+ lsm_plugin_helper/__init__.py \
+ lsm_plugin_helper/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-05-23 15:30:41 UTC
Permalink
* Update lsm.Client.systems() to
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)

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

diff --git a/python_binding/lsm/_client.py b/python_binding/lsm/_client.py
index 3cf6087..53ae41d 100644
--- a/python_binding/lsm/_client.py
+++ b/python_binding/lsm/_client.py
@@ -421,12 +421,17 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of system objects.
@_return_requires([System])
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of system objects. System information is used to
distinguish resources from on storage array to another when the plug=in
supports the ability to have more than one array managed by it
"""
+ if search_key and search_key not in System.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'system_id':
+ search_key = 'id'
return self._tp.rpc('systems', _del_self(locals()))

## Returns an array of initiator objects
--
1.8.3.1
Gris Ge
2014-05-23 15:30:42 UTC
Permalink
* ErrorNumer.UNSUPPORTED_SEARCH_KEY
# Got unsupported 'search_key'.

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

diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index 0ba2965..b06c3c1 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
@@ -516,6 +516,8 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ UNSUPPORTED_SEARCH_KEY = 510
+
_LOCALS = locals()

@staticmethod
--
1.8.3.1
Gris Ge
2014-05-23 15:30:43 UTC
Permalink
New constants:
* lsm.System.SUPPORTED_SEARCH_KEYS
# Containing all supported search keys.
* lsm.Capabilities.SYSTEMS_QUICK_SEARCH
# This capability indicate plugin can perform a quick
# search without pull all data from storage system.
# Without this capability, plugin will pull all data from
# storage system to search given key. It only save socket data
# transfer between plugin and user API. If user want to
# do multiple search, it is suggested to query all systems
# without 'search_key' parameter, then filter in user's code
# space.

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

diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index c72dc54..89e1549 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -440,6 +440,7 @@ The lsm.System class does not have any extra constants.

The lsm.System class does not have class methods.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'name', 'status']

STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
@@ -907,6 +908,8 @@ class Capabilities(IData):

POOL_DELETE = 200

+ SYSTEMS_QUICK_SEARCH = 210
+
def _to_dict(self):
rc = {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
--
1.8.3.1
Gris Ge
2014-05-23 15:30:44 UTC
Permalink
* New lsmcli command line option:
lsmcli list --type systems --sys <SYS_ID>
* Manpage updated.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 4 ++++
tools/lsmcli/cmdline.py | 11 ++++++++++-
2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..950726a 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -158,6 +158,10 @@ Required for \fB--type\fR=\fBSNAPSHOTS\fR. List the snapshots of certain
filesystem.
PLUGINS will list all supported plugins of LSM, not only the current one.
.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Only display resources from system with ID SYS_ID. If defined SYS_ID does not
+exists, no error will be raised, just got empty reply.
+.TP
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
available.
diff --git a/tools/lsmcli/cmdline.py b/tools/lsmcli/cmdline.py
index 9cc0362..5c303c1 100644
--- a/tools/lsmcli/cmdline.py
+++ b/tools/lsmcli/cmdline.py
@@ -174,6 +174,7 @@ cmds = (
default=False,
dest='all',
action='store_true'),
+ dict(sys_id_opt),
dict(fs_id_opt),
],
),
@@ -890,6 +891,12 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.sys:
+ search_key = 'system_id'
+ search_value = args.sys
+
if args.type == 'VOLUMES':
self.display_data(self.c.volumes())
elif args.type == 'POOLS':
@@ -915,7 +922,7 @@ class CmdLine:
elif args.type == 'ACCESS_GROUPS':
self.display_data(self.c.access_groups())
elif args.type == 'SYSTEMS':
- self.display_data(self.c.systems())
+ self.display_data(self.c.systems(search_key,search_value))
elif args.type == 'DISKS':
if args.all:
self.display_data(
@@ -1178,6 +1185,8 @@ class CmdLine:
self._cp("EXPORT_REMOVE", cap.supported(Capabilities.EXPORT_REMOVE))
self._cp("EXPORT_CUSTOM_PATH",
cap.supported(Capabilities.EXPORT_CUSTOM_PATH))
+ self._cp("SYSTEMS_QUICK_SEARCH",
+ cap.supported(Capabilities.SYSTEMS_QUICK_SEARCH))

def plugin_info(self, args):
desc, version = self.c.plugin_info()
--
1.8.3.1
Gris Ge
2014-05-23 15:30:46 UTC
Permalink
* Add a simple system query filter test for lsmcli.

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

diff --git a/test/cmdtest.py b/test/cmdtest.py
index 3042a40..cd8a21c 100755
--- a/test/cmdtest.py
+++ b/test/cmdtest.py
@@ -676,6 +676,12 @@ def create_all(cap, system_id):
test_fs_creation(cap, system_id)
test_nfs(cap, system_id)

+def filter_test(cap, system_id):
+ supported_types = ['systems']
+ supported_filters = [ "--sys='%s'" % system_id ]
+ for resouce_type in supported_types:
+ for supported_filter in supported_filters:
+ call([cmd, 'list', '--type', resouce_type, supported_filter])

def run_all_tests(cap, system_id):
test_display(cap, system_id)
@@ -685,6 +691,7 @@ def run_all_tests(cap, system_id):
create_all(cap, system_id)

test_mapping(cap, system_id)
+ filter_test(cap, system_id)


if __name__ == "__main__":
--
1.8.3.1
Gris Ge
2014-05-23 15:30:45 UTC
Permalink
* Update all existing plugin to use lsm_plugin_helper for system
query filter.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/nstor/nstor.py | 6 +++++-
plugin/ontap/ontap.py | 5 ++++-
plugin/sim/simulator.py | 8 +++++++-
plugin/smispy/smis.py | 5 ++++-
plugin/targetd/targetd.py | 5 ++++-
plugin/v7k/ibmv7k.py | 5 ++++-
6 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index c661090..b821b09 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -32,6 +32,8 @@ from lsm import (AccessGroup, Capabilities, ErrorNumber, FileSystem, INfs,
FsSnapshot, System, VERSION, Volume, md5,
common_urllib2_error_handler)

+from lsm_plugin_helper import system_search
+

class NexentaStor(INfs, IStorageAreaNetwork):
def plugin_info(self, flags=0):
@@ -272,7 +274,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):

return c

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.system]

def fs_resize(self, fs, new_size_bytes, flags=0):
diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index 5e9aa12..d045c64 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -26,6 +26,7 @@ from lsm import (Volume, Initiator, FileSystem, FsSnapshot, NfsExport,
AccessGroup, System, Capabilities, Disk, Pool, OptionalData,
IStorageAreaNetwork, INfs, LsmError, ErrorNumber, JobStatus,
md5, Error, VERSION, common_urllib2_error_handler)
+from lsm_plugin_helper import system_search

#Maps na to lsm, this is expected to expand over time.
e_map = {
@@ -492,7 +493,9 @@ class Ontap(IStorageAreaNetwork, INfs):
return pools

@handle_ontap_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.sys_info]

@handle_ontap_errors
diff --git a/plugin/sim/simulator.py b/plugin/sim/simulator.py
index 22ddfe3..1945b99 100644
--- a/plugin/sim/simulator.py
+++ b/plugin/sim/simulator.py
@@ -20,6 +20,8 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,

from simarray import SimArray

+from lsm_plugin_helper import system_search
+

class SimPlugin(INfs, IStorageAreaNetwork):
"""
@@ -76,12 +78,16 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
rc = Capabilities()
rc.enable_all()
+ rc.set(Capabilities.SYSTEMS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
return rc

def plugin_info(self, flags=0):
return "Storage simulator", VERSION

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
+
sim_syss = self.sim_array.systems()
return [SimPlugin._sim_data_2_lsm(s) for s in sim_syss]

diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index df097c3..3f1c289 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -27,6 +27,7 @@ from pywbem import CIMError
from lsm import (IStorageAreaNetwork, Error, uri_parse, LsmError, ErrorNumber,
JobStatus, md5, Pool, Initiator, Volume, AccessGroup, System,
Capabilities, Disk, OptionalData, txt_a, VERSION)
+from lsm_plugin_helper import system_search

## Variable Naming scheme:
# cim_xxx CIMInstance
@@ -1543,7 +1544,7 @@ class Smis(IStorageAreaNetwork):
return cim_sys_pros

@handle_cim_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
"""
Return the storage arrays accessible from this plug-in at this time

@@ -1551,6 +1552,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
cim_sys_pros = self._cim_sys_pros()
cim_syss = self._root_cim_syss(cim_sys_pros)

diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 0f82349..1289391 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -23,6 +23,7 @@ from lsm import (Pool, Volume, System, Capabilities, Initiator,
IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
LsmError, ErrorNumber, uri_parse, md5, VERSION,
common_urllib2_error_handler)
+from lsm_plugin_helper import system_search


import urllib2
@@ -129,7 +130,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return "Linux LIO target support", VERSION

@handle_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
# verify we're online
self._jsonrequest("pool_list")

diff --git a/plugin/v7k/ibmv7k.py b/plugin/v7k/ibmv7k.py
index f7ef393..58408b7 100644
--- a/plugin/v7k/ibmv7k.py
+++ b/plugin/v7k/ibmv7k.py
@@ -19,6 +19,7 @@ import paramiko

from lsm import (Capabilities, ErrorNumber, IStorageAreaNetwork, Initiator,
LsmError, Pool, System, VERSION, Volume, uri_parse)
+from lsm_plugin_helper import system_search


def handle_ssh_errors(method):
@@ -427,7 +428,9 @@ class IbmV7k(IStorageAreaNetwork):
gp = self._get_pools()
return [self._pool(p) for p in gp.itervalues()]

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.sys_info]

def volumes(self, flags=0):
--
1.8.3.1
Gris Ge
2014-05-25 15:30:08 UTC
Permalink
Post by Gris Ge
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
* "python_binding/lsm_plugin_helper/" provides general system filter helper
====
from lsm_plugin_helper import system_search()
return system_search(search_key, search_value, flags)
====
* Add new capability: lsm.Capabilities.SYSTEMS_QUICK_SEARCH
* Add new constant(list of string): lsm.System.SUPPORTED_SEARCH_KEYS
lsmcli list --type systems --sys <SYS_ID>
* Tests for lsmcli about system query filter.
* There are duplicate codes for converting search_key from 'system_id' into
'id'. It was caused by allowing plugin running standalone.
I have drafted a better one, please review the V2 patch set.

Thanks.
--
Gris Ge
Gris Ge
2014-05-25 16:22:19 UTC
Permalink
* Introduce search support for these query methods
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)
* "python_binding/lsm_plugin_helper/" provides general system filter helper
for plugins, sample codes for lsm.Client.systems():
====
from lsm_plugin_helper import system_search
if search_key:
return system_search(search_key, search_value, flags)
====
* Add new capabilities:
lsm.Capabilities.SYSTEMS_QUICK_SEARCH
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH
* Add new class constant:
lsm.System.SUPPORTED_SEARCH_KEYS
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS
* New ErrorNumber:
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
* lsmcli now allowing '--sys' and etc options in list command.
* Tests for lsmcli about query with search.
* There are duplicate codes for converting search_key from 'system_id' into
'id'. It was caused by allowing plugin running standalone.
* Using 'nfs_export_id' instead of 'export_id'.
Currently, we have lsm.NfsExport and lsm.Client.exports().
We can unify them in other trivial patch.

Gris Ge (7):
Introduce new module: lsm_plugin_helper
lsm_plugin_helper: Update Makefile for new module
lsm_plugin_helper: Update RPM spec file for new module
Python library: Add search support into query methods
lsmcli: Introduce query search support
LSM Plugins: Use plugin_helper to support query search
lsm test: Add query search test

doc/man/lsmcli.1.in | 29 +++++
packaging/libstoragemgmt.spec.in | 2 +
plugin/nstor/nstor.py | 27 +++--
plugin/ontap/ontap.py | 31 ++++--
plugin/sim/simulator.py | 41 ++++++--
plugin/smispy/smis.py | 22 +++-
plugin/targetd/targetd.py | 22 +++-
plugin/v7k/ibmv7k.py | 13 ++-
python_binding/Makefile.am | 6 ++
python_binding/lsm/_client.py | 51 +++++++--
python_binding/lsm/_common.py | 2 +
python_binding/lsm/_data.py | 16 +++
python_binding/lsm_plugin_helper/__init__.py | 20 ++++
python_binding/lsm_plugin_helper/plugin_helper.py | 89 ++++++++++++++++
test/cmdtest.py | 51 +++++++++
tools/lsmcli/cmdline.py | 122 +++++++++++++++++++---
16 files changed, 487 insertions(+), 57 deletions(-)
create mode 100644 python_binding/lsm_plugin_helper/__init__.py
create mode 100644 python_binding/lsm_plugin_helper/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-05-25 16:22:20 UTC
Permalink
* "python_binding/lsm_plugin_helper/" provides general search helper
for plugins, sample codes for lsm.Client.systems():
====
from lsm_plugin_helper import system_search
if search_key:
return system_search(search_key, search_value, flags)
====

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm_plugin_helper/__init__.py | 20 +++++
python_binding/lsm_plugin_helper/plugin_helper.py | 89 +++++++++++++++++++++++
2 files changed, 109 insertions(+)
create mode 100644 python_binding/lsm_plugin_helper/__init__.py
create mode 100644 python_binding/lsm_plugin_helper/plugin_helper.py

diff --git a/python_binding/lsm_plugin_helper/__init__.py b/python_binding/lsm_plugin_helper/__init__.py
new file mode 100644
index 0000000..f19452e
--- /dev/null
+++ b/python_binding/lsm_plugin_helper/__init__.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search, fs_search,
+ nfs_export_search)
diff --git a/python_binding/lsm_plugin_helper/plugin_helper.py b/python_binding/lsm_plugin_helper/plugin_helper.py
new file mode 100644
index 0000000..fe270bc
--- /dev/null
+++ b/python_binding/lsm_plugin_helper/plugin_helper.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from lsm import (LsmError, ErrorNumber, System, Pool, Volume, Disk,
+ AccessGroup, FileSystem, NfsExport)
+
+_CLASS_ID_PRO_KEY = {
+ System: 'system_id',
+ Pool: 'pool_id',
+ Volume: 'volume_id',
+ Disk: 'disk_id',
+ AccessGroup: 'access_group_id',
+ FileSystem: 'fs_id',
+ NfsExport: 'nfs_export_id',
+}
+
+_QUERY_METHOD = {
+ System: 'systems',
+ Pool: 'pools',
+ Volume: 'volumes',
+ Disk: 'disks',
+ AccessGroup: 'access_groups',
+ FileSystem: 'fs',
+ NfsExport: 'exports',
+}
+
+def _search_property(plugin_self, class_obj, search_key, search_value, flags):
+ """
+ This method does not check whether lsm_obj contain requested property.
+ The method caller should do the check.
+ """
+ if class_obj not in _QUERY_METHOD.keys():
+ raise LsmError(ErrorNumber.INTERNAL_ERROR,
+ "BUG: %s is not in _QUERY_METHOD.keys()" % class_obj)
+ # These duplicate codes is for running plugin in standalone mode.
+ if search_key == _CLASS_ID_PRO_KEY[class_obj]:
+ search_key = 'id'
+
+ lsm_objs = getattr(plugin_self, _QUERY_METHOD[class_obj])(
+ search_key=None, search_value=None, flags=flags)
+
+ # _client.py already checked the search_key.
+ # When running in plugin standalone, we might raise incorrect error,
+ # but that's acceptable considering standalone mode is only for plugin
+ # developer.
+ return list(lsm_obj for lsm_obj in lsm_objs
+ if getattr(lsm_obj, search_key) == search_value)
+
+def system_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, System, search_key, search_value, flags)
+
+def pool_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, Pool, search_key, search_value, flags)
+
+def volume_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, Volume, search_key, search_value, flags)
+
+def disk_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, Disk, search_key, search_value, flags)
+
+def access_group_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, AccessGroup, search_key, search_value, flags)
+
+def fs_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, FileSystem, search_key, search_value, flags)
+
+def nfs_export_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, NfsExport, search_key, search_value, flags)
--
1.8.3.1
Gris Ge
2014-05-25 16:22:21 UTC
Permalink
* Makefile update for saving 'lsm_plugin_helper' module into this folder:
$(pythondir)/lsm_plugin_helper

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/Makefile.am | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/python_binding/Makefile.am b/python_binding/Makefile.am
index 8b0581a..158ec03 100644
--- a/python_binding/Makefile.am
+++ b/python_binding/Makefile.am
@@ -15,3 +15,9 @@ lsm_PYTHON = \
external_PYTHON = \
lsm/external/__init__.py \
lsm/external/xmltodict.py
+
+lsm_plugin_helperdir= $(pythondir)/lsm_plugin_helper
+
+lsm_plugin_helper_PYTHON = \
+ lsm_plugin_helper/__init__.py \
+ lsm_plugin_helper/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-05-25 16:22:22 UTC
Permalink
* Update RPM spec file for saving 'lsm_plugin_helper' module files into
'libstoragemgmt-python' package.

Signed-off-by: Gris Ge <***@redhat.com>
---
packaging/libstoragemgmt.spec.in | 2 ++
1 file changed, 2 insertions(+)

diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index b7b391a..58cacb5 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -335,6 +335,8 @@ fi
%{python_sitelib}/lsm/plugin/sim/__init__.*
%{python_sitelib}/lsm/plugin/sim/simulator.*
%{python_sitelib}/lsm/plugin/sim/simarray.*
+%{python_sitelib}/lsm_plugin_helper/__init__.py*
+%{python_sitelib}/lsm_plugin_helper/plugin_helper.py*
%{_bindir}/sim_lsmplugin

%files smis-plugin
--
1.8.3.1
Gris Ge
2014-05-25 16:22:23 UTC
Permalink
* Introduce search support for these query methods
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)

* Add new capabilities:
lsm.Capabilities.SYSTEMS_QUICK_SEARCH
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH

* Add new class constant:
lsm.System.SUPPORTED_SEARCH_KEYS
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS
* Add new ErrorNumber:
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm/_client.py | 51 ++++++++++++++++++++++++++++++++++++-------
python_binding/lsm/_common.py | 2 ++
python_binding/lsm/_data.py | 16 ++++++++++++++
3 files changed, 61 insertions(+), 8 deletions(-)

diff --git a/python_binding/lsm/_client.py b/python_binding/lsm/_client.py
index 3cf6087..ee6d085 100644
--- a/python_binding/lsm/_client.py
+++ b/python_binding/lsm/_client.py
@@ -21,7 +21,7 @@ import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, NfsExport)

from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
@@ -284,11 +284,16 @@ class Client(INetworkAttachedStorage):
# returned.
# @returns An array of pool objects.
@_return_requires([Pool])
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of pool objects. Pools are used in both block and
file system interfaces, thus the reason they are in the base class.
"""
+ if search_key and search_key not in Pool.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'pool_id':
+ search_key = 'id'
return self._tp.rpc('pools', _del_self(locals()))

## Create new pool in user friendly way. Depending on this capability:
@@ -421,12 +426,17 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of system objects.
@_return_requires([System])
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of system objects. System information is used to
distinguish resources from on storage array to another when the plug=in
supports the ability to have more than one array managed by it
"""
+ if search_key and search_key not in System.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'system_id':
+ search_key = 'id'
return self._tp.rpc('systems', _del_self(locals()))

## Returns an array of initiator objects
@@ -517,10 +527,15 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of volume objects.
@_return_requires([Volume])
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects
"""
+ if search_key and search_key not in Volume.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'volume_id':
+ search_key = 'id'
return self._tp.rpc('volumes', _del_self(locals()))

## Creates a volume
@@ -668,10 +683,15 @@ class Client(INetworkAttachedStorage):
# be returned.
# @returns An array of disk objects.
@_return_requires([Disk])
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of disk objects
"""
+ if search_key and search_key not in Disk.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'disk_id':
+ search_key = 'id'
return self._tp.rpc('disks', _del_self(locals()))

## Access control for allowing an access group to access a volume
@@ -706,10 +726,15 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns List of access groups
@_return_requires([AccessGroup])
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups
"""
+ if search_key and search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'access_group_id':
+ search_key = 'id'
return self._tp.rpc('access_groups', _del_self(locals()))

## Creates an access a group with the specified initiator in it.
@@ -836,10 +861,15 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns A list of FS objects.
@_return_requires([FileSystem])
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of file systems on the controller.
"""
+ if search_key and search_key not in FileSystem.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'fs_id':
+ search_key = 'id'
return self._tp.rpc('fs', _del_self(locals()))

## Deletes a file system
@@ -1071,10 +1101,15 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of export objects
@_return_requires([NfsExport])
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
+ if search_key and search_key not in NfsExport.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ if search_key == 'nfs_export_id':
+ search_key = 'id'
return self._tp.rpc('exports', _del_self(locals()))

## Exports a FS as specified in the export.
diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index 0ba2965..b06c3c1 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
@@ -516,6 +516,8 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ UNSUPPORTED_SEARCH_KEY = 510
+
_LOCALS = locals()

@staticmethod
diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index c72dc54..f585fbf 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -115,6 +115,7 @@ class IData(object):
__metaclass__ = _ABCMeta

OPT_PROPERTIES = []
+ SUPPORTED_SEARCH_KEYS = []

def _to_dict(self):
"""
@@ -191,6 +192,7 @@ class Disk(IData):
"""
Represents a disk.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'disk_id']
RETRIEVE_FULL_INFO = 2 # Used by _client.py for disks() call.

# We use '-1' to indicate we failed to get the requested number.
@@ -322,6 +324,7 @@ class Volume(IData):
"""
Represents a volume.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id', 'volume_id']
# Volume status Note: Volumes can have multiple status bits set at same
# time.
(STATUS_UNKNOWN, STATUS_OK, STATUS_DEGRADED, STATUS_ERR, STATUS_STARTING,
@@ -440,6 +443,7 @@ The lsm.System class does not have any extra constants.

The lsm.System class does not have class methods.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
@@ -474,6 +478,7 @@ class Pool(IData):
RETRIEVE_FULL_INFO = 1 # Used by _client.py for pools() call.
# This might not be a good place, please
# suggest a better one.
+ SUPPORTED_SEARCH_KEYS = ['id', 'pool_id', 'system_id']

TOTAL_SPACE_NOT_FOUND = -1
FREE_SPACE_NOT_FOUND = -1
@@ -691,6 +696,7 @@ class Pool(IData):
@default_property('pool_id', doc="What pool the file system resides on")
@default_property('system_id', doc="System ID")
class FileSystem(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id', 'fs_id']
def __init__(self, _id, _name, _total_space, _free_space, _pool_id,
_system_id):
self._id = _id
@@ -722,6 +728,7 @@ class FsSnapshot(IData):
@default_property('anongid', doc="GID for anonymous group id")
@default_property('options', doc="String containing advanced options")
class NfsExport(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'nfs_export_id', 'fs_id']
ANON_UID_GID_NA = -1
ANON_UID_GID_ERROR = (ANON_UID_GID_NA - 1)

@@ -757,6 +764,7 @@ class BlockRange(IData):
@default_property('initiators', doc="List of initiators")
@default_property('system_id', doc="System identifier")
class AccessGroup(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'access_group_id', 'system_id']
def __init__(self, _id, _name, _initiators, _system_id='NA'):
self._id = _id
self._name = _name # AccessGroup name
@@ -907,6 +915,14 @@ class Capabilities(IData):

POOL_DELETE = 200

+ SYSTEMS_QUICK_SEARCH = 210
+ POOLS_QUICK_SEARCH = 211
+ VOLUMES_QUICK_SEARCH = 212
+ DISKS_QUICK_SEARCH = 213
+ ACCESS_GROUPS_QUICK_SEARCH = 214
+ FS_QUICK_SEARCH = 215
+ NFS_EXPORTS_QUICK_SEARCH = 216
+
def _to_dict(self):
rc = {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
--
1.8.3.1
Gris Ge
2014-05-25 16:22:24 UTC
Permalink
* Add support of query search into lsmcli 'list' command for these options:
--sys
--pool
--vol
--disk
--ag
--fs
--nfs-export
* Manpage updated with detail support status.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 29 ++++++++++++
tools/lsmcli/cmdline.py | 122 +++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 135 insertions(+), 16 deletions(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..47fcf6e 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -161,6 +161,35 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
available.
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Search resources from system with SYS_ID. Only supported by these types of
+resources: \fBSYSTEMS\fR, \fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR,
+\fBSNAPSHOTS\fR, \fBDISKS\fR, \fBACCESS_GROUPS\fR.
+.TP
+\fB--pool\fR \fI<POOL_ID>\fR
+Search resources from pool with POOL_ID. Only supported by these types of
+resources: \fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Search resources from volume with VOL_ID. Only supported by these types of
+resources: \fBVOLUMES\fR.
+.TP
+\fB--disk\fR \fI<DISK_ID>\fR
+Search resources from disk with DISK_ID. Only supported by these types of
+resources: \fBDISK\fR.
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Search resources from access group with AG_ID. Only supported by these types
+of resources: \fBACCESS_GROUPS\fR.
+.TP
+\fB--fs\fR \fI<FS_ID>\fR
+Search resources from file system with FS_ID. Only supported by these types
+of resources: \fBFS\fR.
+.TP
+\fB--nfs-export\fR \fI<NFS_EXPORT_ID>\fR
+Search resources from NFS export with NFS_EXPORT_ID. Only supported by these
+types of resources: \fBEXPORTS\fR.

.SS job-status
Retrieve information about a job.
diff --git a/tools/lsmcli/cmdline.py b/tools/lsmcli/cmdline.py
index 9cc0362..33ec19b 100644
--- a/tools/lsmcli/cmdline.py
+++ b/tools/lsmcli/cmdline.py
@@ -28,7 +28,8 @@ from argparse import RawTextHelpFormatter

from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse, Proxy, size_human_2_size_bytes)
+ uri_parse, Proxy, size_human_2_size_bytes, System,
+ AccessGroup, FileSystem, NfsExport)

from lsm.lsmcli.data_display import (
DisplayData, PlugData, out,
@@ -138,14 +139,34 @@ for i in range(0, len(raid_types), 4):
raid_help = "Valid RAID type:" + raid_types_formatted

sys_id_opt = dict(name='--sys', metavar='<SYS_ID>', help='System ID')
+sys_id_filter_opt = sys_id_opt
+sys_id_filter_opt['help'] = 'Search by System ID'
+
pool_id_opt = dict(name='--pool', metavar='<POOL_ID>', help='Pool ID')
+pool_id_filter_opt = pool_id_opt
+pool_id_filter_opt['help'] = 'Search by Pool ID'
+
vol_id_opt = dict(name='--vol', metavar='<VOL_ID>', help='Volume ID')
-fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File system ID')
-ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access group ID')
+vol_id_filter_opt = vol_id_opt
+vol_id_filter_opt['help'] = 'Search by Volume ID'
+
+fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File System ID')
+
+ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access Group ID')
+ag_id_filter_opt = ag_id_opt
+ag_id_filter_opt['help'] = 'Search by Access Group ID'
+
init_id_opt = dict(name='--init', metavar='<INIT_ID>', help='Initiator ID')
snap_id_opt = dict(name='--snap', metavar='<SNAP_ID>', help='Snapshot ID')
export_id_opt = dict(name='--export', metavar='<EXPORT_ID>', help='Export ID')

+nfs_export_id_filter_opt = dict(
+ name='--nfs-export', metavar='<NFS_EXPORT_ID>',
+ help='Search by NFS Export ID')
+
+disk_id_filter_opt = dict(name='--disk', metavar='<DISK_ID>',
+ help='Search by Disk ID')
+
size_opt = dict(name='--size', metavar='<SIZE>', help=size_help)
access_opt = dict(name='--access', metavar='<ACCESS>', help=access_help,
choices=access_types, type=str.upper)
@@ -174,7 +195,13 @@ cmds = (
default=False,
dest='all',
action='store_true'),
+ dict(sys_id_filter_opt),
+ dict(pool_id_filter_opt),
+ dict(vol_id_filter_opt),
+ dict(disk_id_filter_opt),
+ dict(ag_id_filter_opt),
dict(fs_id_opt),
+ dict(nfs_export_id_filter_opt),
],
),

@@ -890,16 +917,50 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.sys:
+ search_key = 'system_id'
+ search_value = args.sys
+ if args.pool:
+ search_key = 'pool_id'
+ search_value = args.pool
+ if args.vol:
+ search_key = 'volume_id'
+ search_value = args.vol
+ if args.disk:
+ search_key = 'disk_id'
+ search_value = args.disk
+ if args.ag:
+ search_key = 'access_group_id'
+ search_value = args.ag
+ if args.fs:
+ search_key = 'fs_id'
+ search_value = args.ag
+ if args.nfs_export:
+ search_key = 'nfs_export_id'
+ search_value = args.nfs_export
+
if args.type == 'VOLUMES':
- self.display_data(self.c.volumes())
+ if search_key and search_key not in Volume.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "volume listing." % search_key)
+ self.display_data(self.c.volumes(search_key, search_value))
elif args.type == 'POOLS':
+ if search_key and search_key not in Pool.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "pool listing." % search_key)
+ flags = 0
if args.all:
- self.display_data(
- self.c.pools(Pool.RETRIEVE_FULL_INFO))
- else:
- self.display_data(self.c.pools())
+ flags |= Pool.RETRIEVE_FULL_INFO
+ self.display_data(
+ self.c.pools(search_key, search_value, flags))
elif args.type == 'FS':
- self.display_data(self.c.fs())
+ if search_key and \
+ search_key not in FileSystem.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "volume listing." % search_key)
+ self.display_data(self.c.fs(search_key, search_value))
elif args.type == 'SNAPSHOTS':
if args.fs is None:
raise ArgError("--fs <file system id> required")
@@ -909,19 +970,34 @@ class CmdLine:
elif args.type == 'INITIATORS':
self.display_data(self.c.initiators())
elif args.type == 'EXPORTS':
- self.display_data(self.c.exports())
+ if search_key and \
+ search_key not in NfsExport.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "NFS Export listing" % search_key)
+ self.display_data(self.c.exports(search_key, search_value))
elif args.type == 'NFS_CLIENT_AUTH':
self.display_nfs_client_authentication()
elif args.type == 'ACCESS_GROUPS':
- self.display_data(self.c.access_groups())
+ if 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(
+ self.c.access_groups(search_key,search_value))
elif args.type == 'SYSTEMS':
- self.display_data(self.c.systems())
+ if search_key and search_key not in System.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "system listing" % search_key)
+ self.display_data(self.c.systems(search_key,search_value))
elif args.type == 'DISKS':
+ if search_key and search_key not in Disk.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "disk listing" % search_key)
+ flags = 0
if args.all:
- self.display_data(
- self.c.disks(Disk.RETRIEVE_FULL_INFO))
- else:
- self.display_data(self.c.disks())
+ flags |= Disk.RETRIEVE_FULL_INFO
+ self.display_data(
+ self.c.disks(search_key, search_value, flags))
elif args.type == 'PLUGINS':
self.display_available_plugins()
else:
@@ -1178,6 +1254,20 @@ class CmdLine:
self._cp("EXPORT_REMOVE", cap.supported(Capabilities.EXPORT_REMOVE))
self._cp("EXPORT_CUSTOM_PATH",
cap.supported(Capabilities.EXPORT_CUSTOM_PATH))
+ self._cp("SYSTEMS_QUICK_SEARCH",
+ cap.supported(Capabilities.SYSTEMS_QUICK_SEARCH))
+ 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))

def plugin_info(self, args):
desc, version = self.c.plugin_info()
--
1.8.3.1
Gris Ge
2014-05-25 16:22:25 UTC
Permalink
* Update all plugins to use plugin_helper for supporting query search.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/nstor/nstor.py | 27 +++++++++++++++++++++------
plugin/ontap/ontap.py | 31 ++++++++++++++++++++++++-------
plugin/sim/simulator.py | 41 ++++++++++++++++++++++++++++++++++-------
plugin/smispy/smis.py | 22 +++++++++++++++++-----
plugin/targetd/targetd.py | 22 +++++++++++++++++-----
plugin/v7k/ibmv7k.py | 13 ++++++++++---
6 files changed, 123 insertions(+), 33 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index c661090..f652568 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -32,6 +32,9 @@ from lsm import (AccessGroup, Capabilities, ErrorNumber, FileSystem, INfs,
FsSnapshot, System, VERSION, Volume, md5,
common_urllib2_error_handler)

+from lsm_plugin_helper import (system_search, pool_search, volume_search,
+ access_group_search, fs_search,
+ nfs_export_search)

class NexentaStor(INfs, IStorageAreaNetwork):
def plugin_info(self, flags=0):
@@ -104,7 +107,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):
float(size[:-1]) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return size

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
pools_list = self._request("get_all_names", "volume", [""])

pools = []
@@ -122,7 +127,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):

return pools

- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
fs_list = self._request("get_all_names", "folder", [""])

fss = []
@@ -272,7 +279,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):

return c

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.system]

def fs_resize(self, fs, new_size_bytes, flags=0):
@@ -350,10 +359,12 @@ class NexentaStor(INfs, IStorageAreaNetwork):
rc.append(m.split('=>')[0])
return rc

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
+ if search_key:
+ return nfs_export_search(self, search_key, search_value, flags)
exp_list = self._request("get_shared_folders", "netstorsvc",
['svc:/network/nfs/server:default', ''])

@@ -417,11 +428,13 @@ class NexentaStor(INfs, IStorageAreaNetwork):
def _calc_group(name):
return 'lsm_' + md5(name)[0:8]

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects

"""
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
vol_list = []
lu_list = self._request("get_names", "zvol", [""])

@@ -693,10 +706,12 @@ class NexentaStor(INfs, IStorageAreaNetwork):
[volume.name, view_number])
return

- def access_group(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups
"""
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
hg_list = self._request("list_hostgroups", "stmf", [])

ag_list = []
diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index 5e9aa12..01af92d 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -26,6 +26,9 @@ from lsm import (Volume, Initiator, FileSystem, FsSnapshot, NfsExport,
AccessGroup, System, Capabilities, Disk, Pool, OptionalData,
IStorageAreaNetwork, INfs, LsmError, ErrorNumber, JobStatus,
md5, Error, VERSION, common_urllib2_error_handler)
+from lsm_plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search, fs_search,
+ nfs_export_search)

#Maps na to lsm, this is expected to expand over time.
e_map = {
@@ -287,7 +290,9 @@ class Ontap(IStorageAreaNetwork, INfs):
self.sys_info.id, opt_data)

@handle_ontap_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
luns = self.f.luns_get_all()
return [self._lun(l) for l in luns]

@@ -471,12 +476,16 @@ class Ontap(IStorageAreaNetwork, INfs):
return "NetApp Filer support", VERSION

@handle_ontap_errors
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return disk_search(self, search_key, search_value, flags)
disks = self.f.disks()
return [self._disk(d, flags) for d in disks]

@handle_ontap_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
pools = []
na_aggrs = self.f.aggregates()
na_disks = []
@@ -492,7 +501,9 @@ class Ontap(IStorageAreaNetwork, INfs):
return pools

@handle_ontap_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.sys_info]

@handle_ontap_errors
@@ -742,7 +753,9 @@ class Ontap(IStorageAreaNetwork, INfs):
self.sys_info.id)

@handle_ontap_errors
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
groups = self.f.igroups()
return [self._access_group(g) for g in groups]

@@ -863,7 +876,9 @@ class Ontap(IStorageAreaNetwork, INfs):
return None

@handle_ontap_errors
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
volumes = self.f.volumes()
pools = self.pools()
return [self._vol(v, pools) for v in volumes]
@@ -1019,9 +1034,11 @@ class Ontap(IStorageAreaNetwork, INfs):
None)

@handle_ontap_errors
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
#Get the file systems once and pass to _export which needs to lookup
#the file system id by name.
+ if search_key:
+ return nfs_export_search(self, search_key, search_value, flags)
v = self.fs()
return [Ontap._export(v, e) for e in self.f.nfs_exports()]

diff --git a/plugin/sim/simulator.py b/plugin/sim/simulator.py
index 22ddfe3..379f9cd 100644
--- a/plugin/sim/simulator.py
+++ b/plugin/sim/simulator.py
@@ -20,6 +20,10 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,

from simarray import SimArray

+from lsm_plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search, fs_search,
+ nfs_export_search)
+

class SimPlugin(INfs, IStorageAreaNetwork):
"""
@@ -76,16 +80,29 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
rc = Capabilities()
rc.enable_all()
+ rc.set(Capabilities.SYSTEMS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.POOLS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.VOLUMES_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.DISKS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.FS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.ACCESS_GROUPS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.NFS_EXPORTS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
return rc

def plugin_info(self, flags=0):
return "Storage simulator", VERSION

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
+
sim_syss = self.sim_array.systems()
return [SimPlugin._sim_data_2_lsm(s) for s in sim_syss]

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
sim_pools = self.sim_array.pools(flags)
return [SimPlugin._sim_data_2_lsm(p) for p in sim_pools]

@@ -115,11 +132,15 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def pool_delete(self, pool, flags=0):
return self.sim_array.pool_delete(pool.id, flags)

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
sim_vols = self.sim_array.volumes()
return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]

- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return disk_search(self, search_key, search_value, flags)
sim_disks = self.sim_array.disks()
return [SimPlugin._sim_data_2_lsm(d) for d in sim_disks]

@@ -162,7 +183,9 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return self.sim_array.volume_online(volume.id, flags)

- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
sim_ags = self.sim_array.ags()
return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]

@@ -235,7 +258,9 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_child_dependency_rm(self, volume, flags=0):
return self.sim_array.volume_child_dependency_rm(volume.id, flags)

- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
sim_fss = self.sim_array.fs()
return [SimPlugin._sim_data_2_lsm(f) for f in sim_fss]

@@ -294,7 +319,9 @@ class SimPlugin(INfs, IStorageAreaNetwork):
# The API should change some day
return ["simple"]

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return nfs_export_search(self, search_key, search_key, flags)
sim_exps = self.sim_array.exports(flags)
return [SimPlugin._sim_data_2_lsm(e) for e in sim_exps]

diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index df097c3..ab7d9f3 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -27,6 +27,8 @@ from pywbem import CIMError
from lsm import (IStorageAreaNetwork, Error, uri_parse, LsmError, ErrorNumber,
JobStatus, md5, Pool, Initiator, Volume, AccessGroup, System,
Capabilities, Disk, OptionalData, txt_a, VERSION)
+from lsm_plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search)

## Variable Naming scheme:
# cim_xxx CIMInstance
@@ -1284,7 +1286,7 @@ class Smis(IStorageAreaNetwork):
return self._new_pool(cim_pools[0])

@handle_cim_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Return all volumes.
We are basing on "Block Services Package" profile version 1.4 or
@@ -1304,6 +1306,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
rc = []
cim_sys_pros = self._property_list_of_id("System")
cim_syss = self._root_cim_syss(cim_sys_pros)
@@ -1412,7 +1416,7 @@ class Smis(IStorageAreaNetwork):
return pool_pros

@handle_cim_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
We are basing on "Block Services Package" profile version 1.4 or
later:
@@ -1426,6 +1430,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
rc = []
cim_pool_pros = self._new_pool_cim_pool_pros(
flags == Pool.RETRIEVE_FULL_INFO)
@@ -1543,7 +1549,7 @@ class Smis(IStorageAreaNetwork):
return cim_sys_pros

@handle_cim_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
"""
Return the storage arrays accessible from this plug-in at this time

@@ -1551,6 +1557,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
cim_sys_pros = self._cim_sys_pros()
cim_syss = self._root_cim_syss(cim_sys_pros)

@@ -2140,7 +2148,9 @@ class Smis(IStorageAreaNetwork):
'Error: access group %s does not exist!' % volume.id)

@handle_cim_errors
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
cim_spc_pros = self._new_access_group_cim_spc_pros()
cim_spcs = self._get_access_groups(property_list=cim_spc_pros)
return [self._new_access_group(cim_spc) for cim_spc in cim_spcs]
@@ -2209,7 +2219,7 @@ class Smis(IStorageAreaNetwork):
pass

@handle_cim_errors
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
"""
return all object of data.Disk.
We are using "Disk Drive Lite Subprofile" v1.4 of SNIA SMI-S for these
@@ -2222,6 +2232,8 @@ class Smis(IStorageAreaNetwork):
use EnumerateInstances(). Which means we have to filter the results
by ourself in case URI contain 'system=xxx'.
"""
+ if search_key:
+ return disk_search(self, search_key, search_value, flags)
rc = []
cim_sys_pros = self._property_list_of_id("System")
if not self.fallback_mode:
diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 0f82349..f5a22de 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -23,6 +23,8 @@ from lsm import (Pool, Volume, System, Capabilities, Initiator,
IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
LsmError, ErrorNumber, uri_parse, md5, VERSION,
common_urllib2_error_handler)
+from lsm_plugin_helper import (system_search, pool_search, volume_search,
+ fs_search, nfs_export_search)


import urllib2
@@ -129,7 +131,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return "Linux LIO target support", VERSION

@handle_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
# verify we're online
self._jsonrequest("pool_list")

@@ -144,7 +148,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

@handle_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
volumes = []
for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
p['type'] == 'block'):
@@ -157,7 +163,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return volumes

@handle_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
pools = []
for pool in self._jsonrequest("pool_list"):
pools.append(Pool(pool['name'], pool['name'], pool['size'],
@@ -307,7 +315,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return inits

@handle_errors
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
rc = []
for fs in self._jsonrequest("fs_list"):
#self, id, name, total_space, free_space, pool_id, system_id
@@ -397,7 +407,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return md5(export_path + opts)

@handle_errors
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return nfs_export_search(self, search_key, search_value, flags)
tmp_exports = {}
exports = []
fs_full_paths = {}
diff --git a/plugin/v7k/ibmv7k.py b/plugin/v7k/ibmv7k.py
index f7ef393..33cdc05 100644
--- a/plugin/v7k/ibmv7k.py
+++ b/plugin/v7k/ibmv7k.py
@@ -19,6 +19,7 @@ import paramiko

from lsm import (Capabilities, ErrorNumber, IStorageAreaNetwork, Initiator,
LsmError, Pool, System, VERSION, Volume, uri_parse)
+from lsm_plugin_helper import system_search, pool_search, volume_search


def handle_ssh_errors(method):
@@ -423,14 +424,20 @@ class IbmV7k(IStorageAreaNetwork):
def plugin_info(self, flags=0):
return "IBM V7000 lsm plugin", VERSION

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
gp = self._get_pools()
return [self._pool(p) for p in gp.itervalues()]

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.sys_info]

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
gv = self._get_volumes()
return [self._volume(v) for v in gv.itervalues()]
--
1.8.3.1
Gris Ge
2014-05-25 16:22:26 UTC
Permalink
* New sub-set of tests:
search_test()
* Test both supported and unsupported search key in 'list' command of lsmcli.
* Since cmdtest.py was dedicated for 'sim://' plugin, there is not
extra capability check[1].

[1] Actually, I found 'cmdtest.py' is in mixed state:
* There are guessing codes assuming testing on unknown plugin.
* There are codes dedicated for simulator.
I would suggest 'cmdtest.py' focusing on 'sim://' and 'simc://' tests.
And let plugtest.py do all the capability driven(real use) tests.
That's why I use some kind of 'hard-coded' variables in search_test()

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

diff --git a/test/cmdtest.py b/test/cmdtest.py
index 3042a40..c8ffa4b 100755
--- a/test/cmdtest.py
+++ b/test/cmdtest.py
@@ -54,6 +54,7 @@ cmd = "lsmcli"
sep = ","
test_pool_name = 'lsm_test_aggr'
test_fs_pool_id = ''
+test_disk_id = 'DISK_ID_00000'

CUR_SYS_ID = None

@@ -676,6 +677,54 @@ 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
+ if test_fs_pool_id:
+ pool_id = test_fs_pool_id
+ else:
+ pool_id = name_to_id(OP_POOL, test_pool_name)
+ pool_id_filter = "--pool='%s'" % pool_id
+
+ vol_id = create_volume(pool_id)
+ vol_id_filter = "--vol='%s'" % vol_id
+
+ disk_id_filter = "--disk='%s'" % test_disk_id
+
+ ag_id = access_group_create(iqn[0], system_id)
+ ag_id_filter = "--ag='%s'" % ag_id
+
+ fs_id = fs_create(pool_id)
+ fs_id_filter = "--fs='%s'" % fs_id
+
+ nfs_export_id = export_fs(fs_id)
+ nfs_export_id_filter = "--nfs-export='%s'" % nfs_export_id
+
+ all_filters = [sys_id_filter, pool_id_filter, vol_id_filter,
+ disk_id_filter, ag_id_filter, fs_id_filter,
+ nfs_export_id_filter]
+
+ supported = {
+ 'systems': [sys_id_filter],
+ 'pools': [sys_id_filter, pool_id_filter],
+ 'volumes': [sys_id_filter, pool_id_filter, vol_id_filter],
+ 'disks': [sys_id_filter, disk_id_filter],
+ 'access_groups': [sys_id_filter, ag_id_filter],
+ 'fs': [sys_id_filter, pool_id_filter, fs_id_filter],
+ 'exports': [fs_id_filter, nfs_export_id_filter],
+ }
+ for resouce_type in supported.keys():
+ for cur_filter in all_filters:
+ if cur_filter in supported[resouce_type]:
+ call([cmd, 'list', '--type', resouce_type, cur_filter])
+ else:
+ call([cmd, 'list', '--type', resouce_type, cur_filter], 2)
+
+ un_export_fs(nfs_export_id)
+ delete_fs(fs_id)
+ access_group_delete(ag_id)
+ volume_delete(vol_id)
+ return

def run_all_tests(cap, system_id):
test_display(cap, system_id)
@@ -686,6 +735,8 @@ def run_all_tests(cap, system_id):

test_mapping(cap, system_id)

+ search_test(cap, system_id)
+

if __name__ == "__main__":
parser = OptionParser()
--
1.8.3.1
Tony Asleson
2014-05-27 18:46:30 UTC
Permalink
Comments:

* The client library is checking for 'volume_id' and replacing with 'id'
before the RPC call is made. Then on the plug-in helper side you check
again and replace again with 'id' as needed. Shouldn't we just be using
'id' to start with as that's what the attribute is on each of the
respective classes anyway? If the CLI wants to use volume_id on the
command line, do it there, but then strip in command line before passing
into library.

* The command line code and the client library are checking to make sure
that the search key is valid. I would do it once in the library where
it's required and remove the duplicate checks in the command line. I
saw and agree with your comment in plug_in_helper about running
standalone may raise an incorrect error, but we could easily address
this by catching the specific exception and raising the correct lsm
exception too.

* In _client.py the same code is used repeatedly with slightly different
values. Please replace with a private method like:

@staticmethod
def _search_validate(search_key, supported_search_keys, obj_id):
if search_key and search_key not in supported_search_keys:
raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
"Unsupported search_key: '%s'" % search_key)
if search_key == obj_id:
return 'id'
return search_key

and use it like this

def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects
"""
search_key = Client._search_validate(
search_key, Volume.SUPPORTED_SEARCH_KEYS, 'volume_id')
return self._tp.rpc('volumes', _del_self(locals()))

* Please move the lsm_plugin_helper stuff to a sub directory in lsm
called plugin. That way the name space will always be lsm.* and we will
only have one install root in site packages directory. We can keep
plugin generic code there.

* With the dynamic nature of python, you only need 1 method to do the
filtering in plugin_helper.py instead of needing one for each type. I
would creating a generic method called filter which takes a list of
objects, a search key and the search value and returns the sub list. I
would also do away with _QUERY_METHOD and _CLASS_ID_PRO_KEY as they
aren't really needed.

Something like:

def filter(obj_list, search_key, search_value):
if search_key:
return list(lsm_obj for lsm_obj in obj_list
if getattr(lsm_obj, search_key) == search_value)
else:
return obj_list


Plugins that don't implement filtering on array would simply do
something like:

return lsm.plugin.filter(volumes, key, value)

* Please run pep8, you have some spots where indents are multiples of 3
instead of 4 etc.

* Eventually it would be good to have the plug-ins that actually support
filtering on the array to have that implemented in the plugin.

* Thanks for including tests for the new functionality!

Please submit an updated patch set and I will get started on C bits.

Regards,
Tony
Post by Gris Ge
* Introduce search support for these query methods
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)
* "python_binding/lsm_plugin_helper/" provides general system filter helper
====
from lsm_plugin_helper import system_search
return system_search(search_key, search_value, flags)
====
lsm.Capabilities.SYSTEMS_QUICK_SEARCH
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH
lsm.System.SUPPORTED_SEARCH_KEYS
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
* lsmcli now allowing '--sys' and etc options in list command.
* Tests for lsmcli about query with search.
* There are duplicate codes for converting search_key from 'system_id' into
'id'. It was caused by allowing plugin running standalone.
* Using 'nfs_export_id' instead of 'export_id'.
Currently, we have lsm.NfsExport and lsm.Client.exports().
We can unify them in other trivial patch.
Introduce new module: lsm_plugin_helper
lsm_plugin_helper: Update Makefile for new module
lsm_plugin_helper: Update RPM spec file for new module
Python library: Add search support into query methods
lsmcli: Introduce query search support
LSM Plugins: Use plugin_helper to support query search
lsm test: Add query search test
doc/man/lsmcli.1.in | 29 +++++
packaging/libstoragemgmt.spec.in | 2 +
plugin/nstor/nstor.py | 27 +++--
plugin/ontap/ontap.py | 31 ++++--
plugin/sim/simulator.py | 41 ++++++--
plugin/smispy/smis.py | 22 +++-
plugin/targetd/targetd.py | 22 +++-
plugin/v7k/ibmv7k.py | 13 ++-
python_binding/Makefile.am | 6 ++
python_binding/lsm/_client.py | 51 +++++++--
python_binding/lsm/_common.py | 2 +
python_binding/lsm/_data.py | 16 +++
python_binding/lsm_plugin_helper/__init__.py | 20 ++++
python_binding/lsm_plugin_helper/plugin_helper.py | 89 ++++++++++++++++
test/cmdtest.py | 51 +++++++++
tools/lsmcli/cmdline.py | 122 +++++++++++++++++++---
16 files changed, 487 insertions(+), 57 deletions(-)
create mode 100644 python_binding/lsm_plugin_helper/__init__.py
create mode 100644 python_binding/lsm_plugin_helper/plugin_helper.py
Gris Ge
2014-05-28 12:41:49 UTC
Permalink
Post by Tony Asleson
* With the dynamic nature of python, you only need 1 method to do the
filtering in plugin_helper.py instead of needing one for each type. I
would creating a generic method called filter which takes a list of
objects, a search key and the search value and returns the sub list. I
would also do away with _QUERY_METHOD and _CLASS_ID_PRO_KEY as they
aren't really needed.
return list(lsm_obj for lsm_obj in obj_list
if getattr(lsm_obj, search_key) == search_value)
return obj_list
Plugins that don't implement filtering on array would simply do
return lsm.plugin.filter(volumes, key, value)
Hi Tony,

Your suggests above only works for property search.
But does not works for relationship search:

lsm.Client.volumes(self, search_key='access_group_id',
search_value='<AG_ID>', flags=0)

And this will be introduced by my coming patch set about lun masking.

Current code might look stupid, but that was dedicated for supporting
future extensive relationship search.
Post by Tony Asleson
Please submit an updated patch set and I will get started on C bits.
Thanks for all the suggests.
Patch set is coming.
Post by Tony Asleson
Regards,
Tony
--
Gris Ge
Tony Asleson
2014-05-28 14:25:26 UTC
Permalink
Post by Gris Ge
Your suggests above only works for property search.
Oh, I forgot about this...
Post by Gris Ge
lsm.Client.volumes(self, search_key='access_group_id',
search_value='<AG_ID>', flags=0)
And this will be introduced by my coming patch set about lun masking.
What does the above method return, Volumes or MaskInfos?

Taken from the Wiki:

Volume mask is also known as "LUN Masking". The lsm.MaskInfo represent a
masking relationship between lsm.Volume and lsm.AccessGroup.

Masking information can be queried via these ways:

* Need lsm.Volume:
lsm.Client.volumes(
self, search_key='access_group_id',
search_value=lsm.AccessGroup.id)
* Need lsm.AccessGroup:
lsm.Client.access_groups(
self, search_key='volume_id', search_value=lsm.Volume.id)
* Need MaskInfo:
lsm.Client.mask_infos(
self, search_key='volume_id', search_value=lsm.Volume.id)
lsm.Client.mask_infos(
self, search_key='access_group_id',
search_value=lsm.AccessGroup.id)

Regards,
Tony
Gris Ge
2014-05-28 14:41:21 UTC
Permalink
Post by Tony Asleson
Post by Gris Ge
Your suggests above only works for property search.
Oh, I forgot about this...
Post by Gris Ge
lsm.Client.volumes(self, search_key='access_group_id',
search_value='<AG_ID>', flags=0)
And this will be introduced by my coming patch set about lun masking.
What does the above method return, Volumes or MaskInfos?
The lsm.Client.volumes() will return a list of lsm.Volume.

BTW, I forgot to remove the unneeded 'search_key' converting in
lsm.plugin_helper.

If there is no other concern in that patch set, can you amend it before
committing or leave to me remove them in another patch after commit?

Thanks.
--
Gris Ge
Tony Asleson
2014-05-28 15:07:53 UTC
Permalink
Post by Gris Ge
Post by Tony Asleson
Post by Gris Ge
Your suggests above only works for property search.
Oh, I forgot about this...
Post by Gris Ge
lsm.Client.volumes(self, search_key='access_group_id',
search_value='<AG_ID>', flags=0)
And this will be introduced by my coming patch set about lun masking.
What does the above method return, Volumes or MaskInfos?
The lsm.Client.volumes() will return a list of lsm.Volume.
BTW, I forgot to remove the unneeded 'search_key' converting in
lsm.plugin_helper.
If there is no other concern in that patch set, can you amend it before
committing or leave to me remove them in another patch after commit?
I'm starting to have a lot of concerns at the moment....

I understand your thinking, but I think with these changes we are adding
too much complexity into the design.

Now a plug-in implementer has to do this for listing volumes:

def volumes(self, search_key, search_value, flags):
if not search_key:
# Return all volumes
else:
if search_key in [ 'id', 'volume_id', 'system_id, 'pool_id' ]:
# Either retrieve all and filter results or search on array
else:
if search_key == 'access_group_id':
# Execute all the code we have today in
# volumes_accessible_by_access_group
elif search_key == 'src_volume_id':
# Search volumes which are the target volume
# of give volume in replication.
elif search_key == 'dst_volume_id':
# Search volumes which are the source volume
# of give volume in replication.
else:
raise Exception("Bad search key")

We would have to duplicate similar logic for access_groups and mask_infos.

One thing I wanted to do with the library is to keep it simple and have
one way to do each thing. Now we are looking at 3 different ways to do
the same thing.

In addition, all these search key values today are different
capabilities, so a user would have to query the search key.

Regards,
Tony
Gris Ge
2014-05-28 15:54:32 UTC
Permalink
Post by Tony Asleson
# Return all volumes
# Either retrieve all and filter results or search on array
# Execute all the code we have today in
# volumes_accessible_by_access_group
# Search volumes which are the target volume
# of give volume in replication.
# Search volumes which are the source volume
# of give volume in replication.
raise Exception("Bad search key")
We would have to duplicate similar logic for access_groups and mask_infos.
No. Simply these lines would be enough:
if search_key:
return lsm.plugin_helper.volume_search(
self, search_key, search_value, flags)

If a plugin intend to speed thing up by doing the search in their own
way, no duplicate codes will be since searching for volume masking is
completely different from replication.
Post by Tony Asleson
One thing I wanted to do with the library is to keep it simple and have
one way to do each thing. Now we are looking at 3 different ways to do
the same thing.
The patch set '[PATCH 0/3] Volume/LUN Masking rework.' introduced a
share code:
lsm.plugin_helper._search_relationship()

It make sure we only have 1 method doing all the relationship search in
plugin_helper.
Post by Tony Asleson
In addition, all these search key values today are different
capabilities, so a user would have to query the search key.
We can unify the search_key by forcing very query supporting the same
set of search_keys. But that means extra works and careful
definition[1].
Since this is just a addition change, we can discuss that after 1.0.

[1] What if a user search volumes() by providing a disk_id, we return
the disks of owner pool or real data holding disks.
Post by Tony Asleson
Regards,
Tony
--
Gris Ge
Gris Ge
2014-05-28 13:34:10 UTC
Permalink
* Introduce search support for these query methods
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)
* "python_binding/lsm/plugin_helper/" provides general system filter helper
for plugins, sample codes for lsm.Client.systems():
====
from lsm.plugin_helper import system_search

def systems(self, search_key=None, search_value=None, flags=0)
if search_key:
return system_search(self, search_key, search_value, flags)
====
* Add new capabilities:
lsm.Capabilities.SYSTEMS_QUICK_SEARCH
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH
* Add new class constant:
lsm.System.SUPPORTED_SEARCH_KEYS
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS
* New ErrorNumber:
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
* lsmcli now allowing '--sys' and etc options in list command for searching.
* Tests for lsmcli about query with search.

Changes since V2:
* De-duplicate the search_key validation codes in _client.py
* Removed 'system_id' to 'id' converting. The lsmcli should do the converting
in stead of the python library.
* The new module is renamed as lsm.plugin_helper.
* PEP8 clean up.

Gris Ge (8):
Introduce new module: lsm.plugin_helper
lsm.plugin_helper: Update Makefile for new module
lsm.plugin_helper: Update RPM spec file for new module
Python library: Add search support into query methods
lsmcli: Introduce query search support
LSM Plugins: Use plugin_helper to support query search
lsm test: Add query search test
PEP8 clean up

doc/man/lsmcli.1.in | 29 +++++
packaging/libstoragemgmt.spec.in | 2 +
plugin/nstor/nstor.py | 36 ++++--
plugin/ontap/ontap.py | 41 +++++--
plugin/sim/simarray.py | 8 +-
plugin/sim/simulator.py | 45 +++++--
plugin/smispy/smis.py | 73 +++++++-----
plugin/targetd/targetd.py | 22 +++-
plugin/v7k/ibmv7k.py | 13 ++-
python_binding/Makefile.am | 6 +
python_binding/lsm/_client.py | 47 +++++---
python_binding/lsm/_common.py | 4 +
python_binding/lsm/_data.py | 23 +++-
python_binding/lsm/_iplugin.py | 5 +-
python_binding/lsm/plugin_helper/__init__.py | 20 ++++
python_binding/lsm/plugin_helper/plugin_helper.py | 97 +++++++++++++++
test/cmdtest.py | 51 ++++++++
tools/lsmcli/cmdline.py | 136 +++++++++++++++++++---
18 files changed, 549 insertions(+), 109 deletions(-)
create mode 100644 python_binding/lsm/plugin_helper/__init__.py
create mode 100644 python_binding/lsm/plugin_helper/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-05-28 13:34:12 UTC
Permalink
* Makefile update for saving 'lsm.plugin_helper' module into this folder:
$(pythondir)/lsm/plugin_helper

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/Makefile.am | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/python_binding/Makefile.am b/python_binding/Makefile.am
index 8b0581a..9d80f9b 100644
--- a/python_binding/Makefile.am
+++ b/python_binding/Makefile.am
@@ -15,3 +15,9 @@ lsm_PYTHON = \
external_PYTHON = \
lsm/external/__init__.py \
lsm/external/xmltodict.py
+
+lsm_plugin_helperdir= $(pythondir)/lsm/plugin_helper
+
+lsm_plugin_helper_PYTHON = \
+ lsm/plugin_helper/__init__.py \
+ lsm/plugin_helper/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-05-28 13:34:11 UTC
Permalink
* "python_binding/lsm/plugin_helper/" provides general search helper
for plugins, sample codes for lsm.Client.systems():
====
from lsm.plugin_helper import system_search

def systems(self, search_key=None, search_value=None, flags=0)
if search_key:
return system_search(self, search_key, search_value, flags)
====
* The duplicated codes is for future relationship search. For example:
self.volumes(search_key='access_group_id', search_value='<AG_ID>')
In order to support this kind of relationship search, we have to isolate
search helpers for each query methods.

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm/plugin_helper/__init__.py | 20 +++++
python_binding/lsm/plugin_helper/plugin_helper.py | 89 +++++++++++++++++++++++
2 files changed, 109 insertions(+)
create mode 100644 python_binding/lsm/plugin_helper/__init__.py
create mode 100644 python_binding/lsm/plugin_helper/plugin_helper.py

diff --git a/python_binding/lsm/plugin_helper/__init__.py b/python_binding/lsm/plugin_helper/__init__.py
new file mode 100644
index 0000000..f19452e
--- /dev/null
+++ b/python_binding/lsm/plugin_helper/__init__.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search, fs_search,
+ nfs_export_search)
diff --git a/python_binding/lsm/plugin_helper/plugin_helper.py b/python_binding/lsm/plugin_helper/plugin_helper.py
new file mode 100644
index 0000000..fe270bc
--- /dev/null
+++ b/python_binding/lsm/plugin_helper/plugin_helper.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from lsm import (LsmError, ErrorNumber, System, Pool, Volume, Disk,
+ AccessGroup, FileSystem, NfsExport)
+
+_CLASS_ID_PRO_KEY = {
+ System: 'system_id',
+ Pool: 'pool_id',
+ Volume: 'volume_id',
+ Disk: 'disk_id',
+ AccessGroup: 'access_group_id',
+ FileSystem: 'fs_id',
+ NfsExport: 'nfs_export_id',
+}
+
+_QUERY_METHOD = {
+ System: 'systems',
+ Pool: 'pools',
+ Volume: 'volumes',
+ Disk: 'disks',
+ AccessGroup: 'access_groups',
+ FileSystem: 'fs',
+ NfsExport: 'exports',
+}
+
+def _search_property(plugin_self, class_obj, search_key, search_value, flags):
+ """
+ This method does not check whether lsm_obj contain requested property.
+ The method caller should do the check.
+ """
+ if class_obj not in _QUERY_METHOD.keys():
+ raise LsmError(ErrorNumber.INTERNAL_ERROR,
+ "BUG: %s is not in _QUERY_METHOD.keys()" % class_obj)
+ # These duplicate codes is for running plugin in standalone mode.
+ if search_key == _CLASS_ID_PRO_KEY[class_obj]:
+ search_key = 'id'
+
+ lsm_objs = getattr(plugin_self, _QUERY_METHOD[class_obj])(
+ search_key=None, search_value=None, flags=flags)
+
+ # _client.py already checked the search_key.
+ # When running in plugin standalone, we might raise incorrect error,
+ # but that's acceptable considering standalone mode is only for plugin
+ # developer.
+ return list(lsm_obj for lsm_obj in lsm_objs
+ if getattr(lsm_obj, search_key) == search_value)
+
+def system_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, System, search_key, search_value, flags)
+
+def pool_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, Pool, search_key, search_value, flags)
+
+def volume_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, Volume, search_key, search_value, flags)
+
+def disk_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, Disk, search_key, search_value, flags)
+
+def access_group_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, AccessGroup, search_key, search_value, flags)
+
+def fs_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, FileSystem, search_key, search_value, flags)
+
+def nfs_export_search(plugin_self, search_key, search_value, flags=0):
+ return _search_property(
+ plugin_self, NfsExport, search_key, search_value, flags)
--
1.8.3.1
Gris Ge
2014-05-28 13:34:13 UTC
Permalink
* Update RPM spec file for saving 'lsm.plugin_helper' module files into
'libstoragemgmt-python' package.

Signed-off-by: Gris Ge <***@redhat.com>
---
packaging/libstoragemgmt.spec.in | 2 ++
1 file changed, 2 insertions(+)

diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index b7b391a..f162cdf 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -335,6 +335,8 @@ fi
%{python_sitelib}/lsm/plugin/sim/__init__.*
%{python_sitelib}/lsm/plugin/sim/simulator.*
%{python_sitelib}/lsm/plugin/sim/simarray.*
+%{python_sitelib}/lsm/plugin_helper/__init__.py*
+%{python_sitelib}/lsm/plugin_helper/plugin_helper.py*
%{_bindir}/sim_lsmplugin

%files smis-plugin
--
1.8.3.1
Gris Ge
2014-05-28 13:34:14 UTC
Permalink
* Introduce search support for these query methods
lsm.Client.systems(self, search_key=None, search_value=None, flags=0)
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)

* Add new capabilities:
lsm.Capabilities.SYSTEMS_QUICK_SEARCH
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH

* Add new class constant:
lsm.System.SUPPORTED_SEARCH_KEYS
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS

* Add new ErrorNumber:
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm/_client.py | 28 ++++++++++++++++++++--------
python_binding/lsm/_common.py | 2 ++
python_binding/lsm/_data.py | 16 ++++++++++++++++
3 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/python_binding/lsm/_client.py b/python_binding/lsm/_client.py
index 3cf6087..0201da8 100644
--- a/python_binding/lsm/_client.py
+++ b/python_binding/lsm/_client.py
@@ -21,7 +21,7 @@ import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, NfsExport)

from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
@@ -40,6 +40,11 @@ def _del_self(d):
del d['self']
return d

+def _check_search_key(search_key, supported_keys):
+ if search_key and search_key not in supported_keys:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ return

## Descriptive exception about daemon not running.
def _raise_no_daemon():
@@ -284,11 +289,12 @@ class Client(INetworkAttachedStorage):
# returned.
# @returns An array of pool objects.
@_return_requires([Pool])
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of pool objects. Pools are used in both block and
file system interfaces, thus the reason they are in the base class.
"""
+ _check_search_key(search_key, Pool.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('pools', _del_self(locals()))

## Create new pool in user friendly way. Depending on this capability:
@@ -421,12 +427,13 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of system objects.
@_return_requires([System])
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of system objects. System information is used to
distinguish resources from on storage array to another when the plug=in
supports the ability to have more than one array managed by it
"""
+ _check_search_key(search_key, System.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('systems', _del_self(locals()))

## Returns an array of initiator objects
@@ -517,10 +524,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of volume objects.
@_return_requires([Volume])
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects
"""
+ _check_search_key(search_key, Volume.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('volumes', _del_self(locals()))

## Creates a volume
@@ -668,10 +676,11 @@ class Client(INetworkAttachedStorage):
# be returned.
# @returns An array of disk objects.
@_return_requires([Disk])
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of disk objects
"""
+ _check_search_key(search_key, Disk.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('disks', _del_self(locals()))

## Access control for allowing an access group to access a volume
@@ -706,10 +715,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns List of access groups
@_return_requires([AccessGroup])
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups
"""
+ _check_search_key(search_key, AccessGroup.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('access_groups', _del_self(locals()))

## Creates an access a group with the specified initiator in it.
@@ -836,10 +846,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns A list of FS objects.
@_return_requires([FileSystem])
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of file systems on the controller.
"""
+ _check_search_key(search_key, FileSystem.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('fs', _del_self(locals()))

## Deletes a file system
@@ -1071,10 +1082,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of export objects
@_return_requires([NfsExport])
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
+ _check_search_key(search_key, NfsExport.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('exports', _del_self(locals()))

## Exports a FS as specified in the export.
diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index 0ba2965..b06c3c1 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
@@ -516,6 +516,8 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ UNSUPPORTED_SEARCH_KEY = 510
+
_LOCALS = locals()

@staticmethod
diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index c72dc54..60fcad8 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -115,6 +115,7 @@ class IData(object):
__metaclass__ = _ABCMeta

OPT_PROPERTIES = []
+ SUPPORTED_SEARCH_KEYS = []

def _to_dict(self):
"""
@@ -191,6 +192,7 @@ class Disk(IData):
"""
Represents a disk.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
RETRIEVE_FULL_INFO = 2 # Used by _client.py for disks() call.

# We use '-1' to indicate we failed to get the requested number.
@@ -322,6 +324,7 @@ class Volume(IData):
"""
Represents a volume.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
# Volume status Note: Volumes can have multiple status bits set at same
# time.
(STATUS_UNKNOWN, STATUS_OK, STATUS_DEGRADED, STATUS_ERR, STATUS_STARTING,
@@ -440,6 +443,7 @@ The lsm.System class does not have any extra constants.

The lsm.System class does not have class methods.
"""
+ SUPPORTED_SEARCH_KEYS = ['id']

STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
@@ -474,6 +478,7 @@ class Pool(IData):
RETRIEVE_FULL_INFO = 1 # Used by _client.py for pools() call.
# This might not be a good place, please
# suggest a better one.
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

TOTAL_SPACE_NOT_FOUND = -1
FREE_SPACE_NOT_FOUND = -1
@@ -691,6 +696,7 @@ class Pool(IData):
@default_property('pool_id', doc="What pool the file system resides on")
@default_property('system_id', doc="System ID")
class FileSystem(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
def __init__(self, _id, _name, _total_space, _free_space, _pool_id,
_system_id):
self._id = _id
@@ -722,6 +728,7 @@ class FsSnapshot(IData):
@default_property('anongid', doc="GID for anonymous group id")
@default_property('options', doc="String containing advanced options")
class NfsExport(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'fs_id']
ANON_UID_GID_NA = -1
ANON_UID_GID_ERROR = (ANON_UID_GID_NA - 1)

@@ -757,6 +764,7 @@ class BlockRange(IData):
@default_property('initiators', doc="List of initiators")
@default_property('system_id', doc="System identifier")
class AccessGroup(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
def __init__(self, _id, _name, _initiators, _system_id='NA'):
self._id = _id
self._name = _name # AccessGroup name
@@ -907,6 +915,14 @@ class Capabilities(IData):

POOL_DELETE = 200

+ SYSTEMS_QUICK_SEARCH = 210
+ POOLS_QUICK_SEARCH = 211
+ VOLUMES_QUICK_SEARCH = 212
+ DISKS_QUICK_SEARCH = 213
+ ACCESS_GROUPS_QUICK_SEARCH = 214
+ FS_QUICK_SEARCH = 215
+ NFS_EXPORTS_QUICK_SEARCH = 216
+
def _to_dict(self):
rc = {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
--
1.8.3.1
Gris Ge
2014-05-28 13:34:15 UTC
Permalink
* Add support of query search into lsmcli 'list' command for these options:
--sys
--pool
--vol
--disk
--ag
--fs
--nfs-export
* Manpage updated with detail support status.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 29 +++++++++++
tools/lsmcli/cmdline.py | 136 ++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 149 insertions(+), 16 deletions(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..47fcf6e 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -161,6 +161,35 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
available.
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Search resources from system with SYS_ID. Only supported by these types of
+resources: \fBSYSTEMS\fR, \fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR,
+\fBSNAPSHOTS\fR, \fBDISKS\fR, \fBACCESS_GROUPS\fR.
+.TP
+\fB--pool\fR \fI<POOL_ID>\fR
+Search resources from pool with POOL_ID. Only supported by these types of
+resources: \fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Search resources from volume with VOL_ID. Only supported by these types of
+resources: \fBVOLUMES\fR.
+.TP
+\fB--disk\fR \fI<DISK_ID>\fR
+Search resources from disk with DISK_ID. Only supported by these types of
+resources: \fBDISK\fR.
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Search resources from access group with AG_ID. Only supported by these types
+of resources: \fBACCESS_GROUPS\fR.
+.TP
+\fB--fs\fR \fI<FS_ID>\fR
+Search resources from file system with FS_ID. Only supported by these types
+of resources: \fBFS\fR.
+.TP
+\fB--nfs-export\fR \fI<NFS_EXPORT_ID>\fR
+Search resources from NFS export with NFS_EXPORT_ID. Only supported by these
+types of resources: \fBEXPORTS\fR.

.SS job-status
Retrieve information about a job.
diff --git a/tools/lsmcli/cmdline.py b/tools/lsmcli/cmdline.py
index 9cc0362..335924f 100644
--- a/tools/lsmcli/cmdline.py
+++ b/tools/lsmcli/cmdline.py
@@ -28,7 +28,8 @@ from argparse import RawTextHelpFormatter

from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse, Proxy, size_human_2_size_bytes)
+ uri_parse, Proxy, size_human_2_size_bytes, System,
+ AccessGroup, FileSystem, NfsExport)

from lsm.lsmcli.data_display import (
DisplayData, PlugData, out,
@@ -138,14 +139,34 @@ for i in range(0, len(raid_types), 4):
raid_help = "Valid RAID type:" + raid_types_formatted

sys_id_opt = dict(name='--sys', metavar='<SYS_ID>', help='System ID')
+sys_id_filter_opt = sys_id_opt
+sys_id_filter_opt['help'] = 'Search by System ID'
+
pool_id_opt = dict(name='--pool', metavar='<POOL_ID>', help='Pool ID')
+pool_id_filter_opt = pool_id_opt
+pool_id_filter_opt['help'] = 'Search by Pool ID'
+
vol_id_opt = dict(name='--vol', metavar='<VOL_ID>', help='Volume ID')
-fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File system ID')
-ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access group ID')
+vol_id_filter_opt = vol_id_opt
+vol_id_filter_opt['help'] = 'Search by Volume ID'
+
+fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File System ID')
+
+ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access Group ID')
+ag_id_filter_opt = ag_id_opt
+ag_id_filter_opt['help'] = 'Search by Access Group ID'
+
init_id_opt = dict(name='--init', metavar='<INIT_ID>', help='Initiator ID')
snap_id_opt = dict(name='--snap', metavar='<SNAP_ID>', help='Snapshot ID')
export_id_opt = dict(name='--export', metavar='<EXPORT_ID>', help='Export ID')

+nfs_export_id_filter_opt = dict(
+ name='--nfs-export', metavar='<NFS_EXPORT_ID>',
+ help='Search by NFS Export ID')
+
+disk_id_filter_opt = dict(name='--disk', metavar='<DISK_ID>',
+ help='Search by Disk ID')
+
size_opt = dict(name='--size', metavar='<SIZE>', help=size_help)
access_opt = dict(name='--access', metavar='<ACCESS>', help=access_help,
choices=access_types, type=str.upper)
@@ -174,7 +195,13 @@ cmds = (
default=False,
dest='all',
action='store_true'),
+ dict(sys_id_filter_opt),
+ dict(pool_id_filter_opt),
+ dict(vol_id_filter_opt),
+ dict(disk_id_filter_opt),
+ dict(ag_id_filter_opt),
dict(fs_id_opt),
+ dict(nfs_export_id_filter_opt),
],
),

@@ -890,16 +917,56 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.sys:
+ search_key = 'system_id'
+ search_value = args.sys
+ if args.pool:
+ search_key = 'pool_id'
+ search_value = args.pool
+ if args.vol:
+ search_key = 'volume_id'
+ search_value = args.vol
+ if args.disk:
+ search_key = 'disk_id'
+ search_value = args.disk
+ if args.ag:
+ search_key = 'access_group_id'
+ search_value = args.ag
+ if args.fs:
+ search_key = 'fs_id'
+ search_value = args.ag
+ if args.nfs_export:
+ search_key = 'nfs_export_id'
+ search_value = args.nfs_export
+
if args.type == 'VOLUMES':
- self.display_data(self.c.volumes())
+ if search_key == 'volume_id':
+ search_key = 'id'
+ if search_key and search_key not in Volume.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "volume listing." % search_key)
+ self.display_data(self.c.volumes(search_key, search_value))
elif args.type == 'POOLS':
+ if search_key == 'pool_id':
+ search_key = 'id'
+ if search_key and search_key not in Pool.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "pool listing." % search_key)
+ flags = 0
if args.all:
- self.display_data(
- self.c.pools(Pool.RETRIEVE_FULL_INFO))
- else:
- self.display_data(self.c.pools())
+ flags |= Pool.RETRIEVE_FULL_INFO
+ self.display_data(
+ self.c.pools(search_key, search_value, flags))
elif args.type == 'FS':
- self.display_data(self.c.fs())
+ if search_key == 'fs_id':
+ search_key = 'id'
+ if search_key and \
+ search_key not in FileSystem.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "volume listing." % search_key)
+ self.display_data(self.c.fs(search_key, search_value))
elif args.type == 'SNAPSHOTS':
if args.fs is None:
raise ArgError("--fs <file system id> required")
@@ -909,19 +976,42 @@ class CmdLine:
elif args.type == 'INITIATORS':
self.display_data(self.c.initiators())
elif args.type == 'EXPORTS':
- self.display_data(self.c.exports())
+ if search_key == 'nfs_export_id':
+ search_key = 'id'
+ if search_key and \
+ search_key not in NfsExport.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "NFS Export listing" % search_key)
+ self.display_data(self.c.exports(search_key, search_value))
elif args.type == 'NFS_CLIENT_AUTH':
self.display_nfs_client_authentication()
elif args.type == 'ACCESS_GROUPS':
- self.display_data(self.c.access_groups())
+ if search_key == 'access_group_id':
+ search_key = 'id'
+ if 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(
+ self.c.access_groups(search_key,search_value))
elif args.type == 'SYSTEMS':
- self.display_data(self.c.systems())
+ if search_key == 'system_id':
+ search_key = 'id'
+ if search_key and search_key not in System.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "system listing" % search_key)
+ self.display_data(self.c.systems(search_key,search_value))
elif args.type == 'DISKS':
+ if search_key == 'disk_id':
+ search_key = 'id'
+ if search_key and search_key not in Disk.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "disk listing" % search_key)
+ flags = 0
if args.all:
- self.display_data(
- self.c.disks(Disk.RETRIEVE_FULL_INFO))
- else:
- self.display_data(self.c.disks())
+ flags |= Disk.RETRIEVE_FULL_INFO
+ self.display_data(
+ self.c.disks(search_key, search_value, flags))
elif args.type == 'PLUGINS':
self.display_available_plugins()
else:
@@ -1178,6 +1268,20 @@ class CmdLine:
self._cp("EXPORT_REMOVE", cap.supported(Capabilities.EXPORT_REMOVE))
self._cp("EXPORT_CUSTOM_PATH",
cap.supported(Capabilities.EXPORT_CUSTOM_PATH))
+ self._cp("SYSTEMS_QUICK_SEARCH",
+ cap.supported(Capabilities.SYSTEMS_QUICK_SEARCH))
+ 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))

def plugin_info(self, args):
desc, version = self.c.plugin_info()
--
1.8.3.1
Gris Ge
2014-05-28 13:34:16 UTC
Permalink
* Update all plugins to use lsm.plugin_helper for query search feature.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/nstor/nstor.py | 27 +++++++++++++++++++++------
plugin/ontap/ontap.py | 31 ++++++++++++++++++++++++-------
plugin/sim/simulator.py | 41 ++++++++++++++++++++++++++++++++++-------
plugin/smispy/smis.py | 22 +++++++++++++++++-----
plugin/targetd/targetd.py | 22 +++++++++++++++++-----
plugin/v7k/ibmv7k.py | 13 ++++++++++---
6 files changed, 123 insertions(+), 33 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index c661090..713d041 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -32,6 +32,9 @@ from lsm import (AccessGroup, Capabilities, ErrorNumber, FileSystem, INfs,
FsSnapshot, System, VERSION, Volume, md5,
common_urllib2_error_handler)

+from lsm.plugin_helper import (system_search, pool_search, volume_search,
+ access_group_search, fs_search,
+ nfs_export_search)

class NexentaStor(INfs, IStorageAreaNetwork):
def plugin_info(self, flags=0):
@@ -104,7 +107,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):
float(size[:-1]) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return size

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
pools_list = self._request("get_all_names", "volume", [""])

pools = []
@@ -122,7 +127,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):

return pools

- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
fs_list = self._request("get_all_names", "folder", [""])

fss = []
@@ -272,7 +279,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):

return c

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.system]

def fs_resize(self, fs, new_size_bytes, flags=0):
@@ -350,10 +359,12 @@ class NexentaStor(INfs, IStorageAreaNetwork):
rc.append(m.split('=>')[0])
return rc

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
+ if search_key:
+ return nfs_export_search(self, search_key, search_value, flags)
exp_list = self._request("get_shared_folders", "netstorsvc",
['svc:/network/nfs/server:default', ''])

@@ -417,11 +428,13 @@ class NexentaStor(INfs, IStorageAreaNetwork):
def _calc_group(name):
return 'lsm_' + md5(name)[0:8]

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects

"""
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
vol_list = []
lu_list = self._request("get_names", "zvol", [""])

@@ -693,10 +706,12 @@ class NexentaStor(INfs, IStorageAreaNetwork):
[volume.name, view_number])
return

- def access_group(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups
"""
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
hg_list = self._request("list_hostgroups", "stmf", [])

ag_list = []
diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index 5e9aa12..a82f6e2 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -26,6 +26,9 @@ from lsm import (Volume, Initiator, FileSystem, FsSnapshot, NfsExport,
AccessGroup, System, Capabilities, Disk, Pool, OptionalData,
IStorageAreaNetwork, INfs, LsmError, ErrorNumber, JobStatus,
md5, Error, VERSION, common_urllib2_error_handler)
+from lsm.plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search, fs_search,
+ nfs_export_search)

#Maps na to lsm, this is expected to expand over time.
e_map = {
@@ -287,7 +290,9 @@ class Ontap(IStorageAreaNetwork, INfs):
self.sys_info.id, opt_data)

@handle_ontap_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
luns = self.f.luns_get_all()
return [self._lun(l) for l in luns]

@@ -471,12 +476,16 @@ class Ontap(IStorageAreaNetwork, INfs):
return "NetApp Filer support", VERSION

@handle_ontap_errors
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return disk_search(self, search_key, search_value, flags)
disks = self.f.disks()
return [self._disk(d, flags) for d in disks]

@handle_ontap_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
pools = []
na_aggrs = self.f.aggregates()
na_disks = []
@@ -492,7 +501,9 @@ class Ontap(IStorageAreaNetwork, INfs):
return pools

@handle_ontap_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.sys_info]

@handle_ontap_errors
@@ -742,7 +753,9 @@ class Ontap(IStorageAreaNetwork, INfs):
self.sys_info.id)

@handle_ontap_errors
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
groups = self.f.igroups()
return [self._access_group(g) for g in groups]

@@ -863,7 +876,9 @@ class Ontap(IStorageAreaNetwork, INfs):
return None

@handle_ontap_errors
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
volumes = self.f.volumes()
pools = self.pools()
return [self._vol(v, pools) for v in volumes]
@@ -1019,9 +1034,11 @@ class Ontap(IStorageAreaNetwork, INfs):
None)

@handle_ontap_errors
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
#Get the file systems once and pass to _export which needs to lookup
#the file system id by name.
+ if search_key:
+ return nfs_export_search(self, search_key, search_value, flags)
v = self.fs()
return [Ontap._export(v, e) for e in self.f.nfs_exports()]

diff --git a/plugin/sim/simulator.py b/plugin/sim/simulator.py
index 22ddfe3..ac78950 100644
--- a/plugin/sim/simulator.py
+++ b/plugin/sim/simulator.py
@@ -20,6 +20,10 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,

from simarray import SimArray

+from lsm.plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search, fs_search,
+ nfs_export_search)
+

class SimPlugin(INfs, IStorageAreaNetwork):
"""
@@ -76,16 +80,29 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
rc = Capabilities()
rc.enable_all()
+ rc.set(Capabilities.SYSTEMS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.POOLS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.VOLUMES_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.DISKS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.FS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.ACCESS_GROUPS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.NFS_EXPORTS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
return rc

def plugin_info(self, flags=0):
return "Storage simulator", VERSION

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
+
sim_syss = self.sim_array.systems()
return [SimPlugin._sim_data_2_lsm(s) for s in sim_syss]

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
sim_pools = self.sim_array.pools(flags)
return [SimPlugin._sim_data_2_lsm(p) for p in sim_pools]

@@ -115,11 +132,15 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def pool_delete(self, pool, flags=0):
return self.sim_array.pool_delete(pool.id, flags)

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
sim_vols = self.sim_array.volumes()
return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]

- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return disk_search(self, search_key, search_value, flags)
sim_disks = self.sim_array.disks()
return [SimPlugin._sim_data_2_lsm(d) for d in sim_disks]

@@ -162,7 +183,9 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return self.sim_array.volume_online(volume.id, flags)

- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
sim_ags = self.sim_array.ags()
return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]

@@ -235,7 +258,9 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_child_dependency_rm(self, volume, flags=0):
return self.sim_array.volume_child_dependency_rm(volume.id, flags)

- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
sim_fss = self.sim_array.fs()
return [SimPlugin._sim_data_2_lsm(f) for f in sim_fss]

@@ -294,7 +319,9 @@ class SimPlugin(INfs, IStorageAreaNetwork):
# The API should change some day
return ["simple"]

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return nfs_export_search(self, search_key, search_key, flags)
sim_exps = self.sim_array.exports(flags)
return [SimPlugin._sim_data_2_lsm(e) for e in sim_exps]

diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index df097c3..d6e958b 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -27,6 +27,8 @@ from pywbem import CIMError
from lsm import (IStorageAreaNetwork, Error, uri_parse, LsmError, ErrorNumber,
JobStatus, md5, Pool, Initiator, Volume, AccessGroup, System,
Capabilities, Disk, OptionalData, txt_a, VERSION)
+from lsm.plugin_helper import (system_search, pool_search, volume_search,
+ disk_search, access_group_search)

## Variable Naming scheme:
# cim_xxx CIMInstance
@@ -1284,7 +1286,7 @@ class Smis(IStorageAreaNetwork):
return self._new_pool(cim_pools[0])

@handle_cim_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Return all volumes.
We are basing on "Block Services Package" profile version 1.4 or
@@ -1304,6 +1306,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
rc = []
cim_sys_pros = self._property_list_of_id("System")
cim_syss = self._root_cim_syss(cim_sys_pros)
@@ -1412,7 +1416,7 @@ class Smis(IStorageAreaNetwork):
return pool_pros

@handle_cim_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
We are basing on "Block Services Package" profile version 1.4 or
later:
@@ -1426,6 +1430,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
rc = []
cim_pool_pros = self._new_pool_cim_pool_pros(
flags == Pool.RETRIEVE_FULL_INFO)
@@ -1543,7 +1549,7 @@ class Smis(IStorageAreaNetwork):
return cim_sys_pros

@handle_cim_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
"""
Return the storage arrays accessible from this plug-in at this time

@@ -1551,6 +1557,8 @@ class Smis(IStorageAreaNetwork):
don't check support status here as startup() already checked 'Array'
profile.
"""
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
cim_sys_pros = self._cim_sys_pros()
cim_syss = self._root_cim_syss(cim_sys_pros)

@@ -2140,7 +2148,9 @@ class Smis(IStorageAreaNetwork):
'Error: access group %s does not exist!' % volume.id)

@handle_cim_errors
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return access_group_search(self, search_key, search_value, flags)
cim_spc_pros = self._new_access_group_cim_spc_pros()
cim_spcs = self._get_access_groups(property_list=cim_spc_pros)
return [self._new_access_group(cim_spc) for cim_spc in cim_spcs]
@@ -2209,7 +2219,7 @@ class Smis(IStorageAreaNetwork):
pass

@handle_cim_errors
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
"""
return all object of data.Disk.
We are using "Disk Drive Lite Subprofile" v1.4 of SNIA SMI-S for these
@@ -2222,6 +2232,8 @@ class Smis(IStorageAreaNetwork):
use EnumerateInstances(). Which means we have to filter the results
by ourself in case URI contain 'system=xxx'.
"""
+ if search_key:
+ return disk_search(self, search_key, search_value, flags)
rc = []
cim_sys_pros = self._property_list_of_id("System")
if not self.fallback_mode:
diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 0f82349..c1ba97c 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -23,6 +23,8 @@ from lsm import (Pool, Volume, System, Capabilities, Initiator,
IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
LsmError, ErrorNumber, uri_parse, md5, VERSION,
common_urllib2_error_handler)
+from lsm.plugin_helper import (system_search, pool_search, volume_search,
+ fs_search, nfs_export_search)


import urllib2
@@ -129,7 +131,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return "Linux LIO target support", VERSION

@handle_errors
- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
# verify we're online
self._jsonrequest("pool_list")

@@ -144,7 +148,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

@handle_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
volumes = []
for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
p['type'] == 'block'):
@@ -157,7 +163,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return volumes

@handle_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
pools = []
for pool in self._jsonrequest("pool_list"):
pools.append(Pool(pool['name'], pool['name'], pool['size'],
@@ -307,7 +315,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return inits

@handle_errors
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return fs_search(self, search_key, search_value, flags)
rc = []
for fs in self._jsonrequest("fs_list"):
#self, id, name, total_space, free_space, pool_id, system_id
@@ -397,7 +407,9 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return md5(export_path + opts)

@handle_errors
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return nfs_export_search(self, search_key, search_value, flags)
tmp_exports = {}
exports = []
fs_full_paths = {}
diff --git a/plugin/v7k/ibmv7k.py b/plugin/v7k/ibmv7k.py
index f7ef393..22a37ef 100644
--- a/plugin/v7k/ibmv7k.py
+++ b/plugin/v7k/ibmv7k.py
@@ -19,6 +19,7 @@ import paramiko

from lsm import (Capabilities, ErrorNumber, IStorageAreaNetwork, Initiator,
LsmError, Pool, System, VERSION, Volume, uri_parse)
+from lsm.plugin_helper import system_search, pool_search, volume_search


def handle_ssh_errors(method):
@@ -423,14 +424,20 @@ class IbmV7k(IStorageAreaNetwork):
def plugin_info(self, flags=0):
return "IBM V7000 lsm plugin", VERSION

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return pool_search(self, search_key, search_value, flags)
gp = self._get_pools()
return [self._pool(p) for p in gp.itervalues()]

- def systems(self, flags=0):
+ def systems(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return system_search(self, search_key, search_value, flags)
return [self.sys_info]

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ if search_key:
+ return volume_search(self, search_key, search_value, flags)
gv = self._get_volumes()
return [self._volume(v) for v in gv.itervalues()]
--
1.8.3.1
Gris Ge
2014-05-28 13:34:17 UTC
Permalink
* New sub-set of tests:
search_test()
* Test both supported and unsupported search key in 'list' command of lsmcli.
* Since cmdtest.py was dedicated for 'sim://' plugin, there is not
extra capability check[1].

[1] Actually, I found 'cmdtest.py' is in mixed state:
* There are guessing codes assuming testing on unknown plugin.
* There are codes dedicated for simulator.
I would suggest 'cmdtest.py' focusing on 'sim://' and 'simc://' tests.
And let plugtest.py do all the capability driven(real use) tests.
That's why I use some kind of 'hard-coded' variables in search_test()

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

diff --git a/test/cmdtest.py b/test/cmdtest.py
index 3042a40..c8ffa4b 100755
--- a/test/cmdtest.py
+++ b/test/cmdtest.py
@@ -54,6 +54,7 @@ cmd = "lsmcli"
sep = ","
test_pool_name = 'lsm_test_aggr'
test_fs_pool_id = ''
+test_disk_id = 'DISK_ID_00000'

CUR_SYS_ID = None

@@ -676,6 +677,54 @@ 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
+ if test_fs_pool_id:
+ pool_id = test_fs_pool_id
+ else:
+ pool_id = name_to_id(OP_POOL, test_pool_name)
+ pool_id_filter = "--pool='%s'" % pool_id
+
+ vol_id = create_volume(pool_id)
+ vol_id_filter = "--vol='%s'" % vol_id
+
+ disk_id_filter = "--disk='%s'" % test_disk_id
+
+ ag_id = access_group_create(iqn[0], system_id)
+ ag_id_filter = "--ag='%s'" % ag_id
+
+ fs_id = fs_create(pool_id)
+ fs_id_filter = "--fs='%s'" % fs_id
+
+ nfs_export_id = export_fs(fs_id)
+ nfs_export_id_filter = "--nfs-export='%s'" % nfs_export_id
+
+ all_filters = [sys_id_filter, pool_id_filter, vol_id_filter,
+ disk_id_filter, ag_id_filter, fs_id_filter,
+ nfs_export_id_filter]
+
+ supported = {
+ 'systems': [sys_id_filter],
+ 'pools': [sys_id_filter, pool_id_filter],
+ 'volumes': [sys_id_filter, pool_id_filter, vol_id_filter],
+ 'disks': [sys_id_filter, disk_id_filter],
+ 'access_groups': [sys_id_filter, ag_id_filter],
+ 'fs': [sys_id_filter, pool_id_filter, fs_id_filter],
+ 'exports': [fs_id_filter, nfs_export_id_filter],
+ }
+ for resouce_type in supported.keys():
+ for cur_filter in all_filters:
+ if cur_filter in supported[resouce_type]:
+ call([cmd, 'list', '--type', resouce_type, cur_filter])
+ else:
+ call([cmd, 'list', '--type', resouce_type, cur_filter], 2)
+
+ un_export_fs(nfs_export_id)
+ delete_fs(fs_id)
+ access_group_delete(ag_id)
+ volume_delete(vol_id)
+ return

def run_all_tests(cap, system_id):
test_display(cap, system_id)
@@ -686,6 +735,8 @@ def run_all_tests(cap, system_id):

test_mapping(cap, system_id)

+ search_test(cap, system_id)
+

if __name__ == "__main__":
parser = OptionParser()
--
1.8.3.1
Gris Ge
2014-05-28 13:34:18 UTC
Permalink
* Just python-PEP8 clean up.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/nstor/nstor.py | 9 ++--
plugin/ontap/ontap.py | 10 ++---
plugin/sim/simarray.py | 8 ++--
plugin/sim/simulator.py | 4 +-
plugin/smispy/smis.py | 51 ++++++++++++-----------
python_binding/lsm/_client.py | 19 +++++----
python_binding/lsm/_common.py | 2 +
python_binding/lsm/_data.py | 7 +++-
python_binding/lsm/_iplugin.py | 5 ++-
python_binding/lsm/plugin_helper/plugin_helper.py | 8 ++++
10 files changed, 71 insertions(+), 52 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index 713d041..9c8b3f9 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -36,6 +36,7 @@ from lsm.plugin_helper import (system_search, pool_search, volume_search,
access_group_search, fs_search,
nfs_export_search)

+
class NexentaStor(INfs, IStorageAreaNetwork):
def plugin_info(self, flags=0):
# TODO: Change this to something more appropriate
@@ -178,7 +179,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
snapshot_info = self._request("get_child_props", "snapshot",
[snapshot, "creation_seconds"])
snapshots.append(FsSnapshot(snapshot, snapshot,
- snapshot_info['creation_seconds']))
+ snapshot_info['creation_seconds']))

return snapshots

@@ -189,7 +190,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
snapshot_info = self._request("get_child_props", "snapshot",
[full_name, "creation_seconds"])
return None, FsSnapshot(full_name, full_name,
- snapshot_info['creation_seconds'])
+ snapshot_info['creation_seconds'])

def fs_snapshot_delete(self, fs, snapshot, flags=0):
self._request("destroy", "snapshot", [snapshot.name, ""])
@@ -311,11 +312,11 @@ class NexentaStor(INfs, IStorageAreaNetwork):
return None, fs

def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
return

def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
self._request("rollback", "snapshot", [snapshot.name, '-r'])
return

diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index a82f6e2..06c0ed5 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -205,7 +205,7 @@ class Ontap(IStorageAreaNetwork, INfs):
#If we use the newer API we can use the uuid instead of this fake
#md5 one
return FsSnapshot(md5(s['name'] + s['access-time']), s['name'],
- s['access-time'])
+ s['access-time'])

@staticmethod
def _disk_type(netapp_disk_type):
@@ -911,7 +911,7 @@ class Ontap(IStorageAreaNetwork, INfs):

@handle_ontap_errors
def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
full_src = Ontap.build_name(fs.name, src_file_name)
full_dest = Ontap.build_name(fs.name, dest_file_name)

@@ -938,7 +938,7 @@ class Ontap(IStorageAreaNetwork, INfs):
self.f.snapshot_delete(fs.name, snapshot.name)

def _ss_restore_files(self, volume_name, snapshot_name, files,
- restore_files):
+ restore_files):
for i in range(len(files)):
src = Ontap.build_name(volume_name, files[i])
dest = None
@@ -948,7 +948,7 @@ class Ontap(IStorageAreaNetwork, INfs):

@handle_ontap_errors
def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
"""
Restores a FS or files on a FS.
Note: Restoring an individual file is a O(n) operation, i.e. time it
@@ -964,7 +964,7 @@ class Ontap(IStorageAreaNetwork, INfs):
"num files != num restore_files")

self._ss_restore_files(fs.name, snapshot.name, files,
- restore_files)
+ restore_files)
return "%s@%d" % (Ontap.SS_JOB, len(files))
else:
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
diff --git a/plugin/sim/simarray.py b/plugin/sim/simarray.py
index 4ded765..32bd17a 100644
--- a/plugin/sim/simarray.py
+++ b/plugin/sim/simarray.py
@@ -27,8 +27,8 @@ import time

from lsm._common import (size_human_2_size_bytes, size_bytes_2_size_human)
from lsm import (System, Volume, Disk, Pool, FileSystem, AccessGroup,
- Initiator, FsSnapshot, NfsExport, OptionalData, md5,
- LsmError, ErrorNumber, JobStatus)
+ Initiator, FsSnapshot, NfsExport, OptionalData, md5,
+ LsmError, ErrorNumber, JobStatus)

# Used for format width for disks
D_FMT = 5
@@ -296,7 +296,7 @@ class SimArray(object):
@staticmethod
def _sim_snap_2_lsm(sim_snap):
return FsSnapshot(sim_snap['snap_id'], sim_snap['name'],
- sim_snap['timestamp'])
+ sim_snap['timestamp'])

def fs_snapshots(self, fs_id, flags=0):
sim_snaps = self.data.fs_snapshots(fs_id, flags)
@@ -1348,7 +1348,7 @@ class SimData(object):
return None

def fs_snapshot_restore(self, fs_id, snap_id, files, restore_files,
- flag_all_files, flags):
+ flag_all_files, flags):
if fs_id not in self.fs_dict.keys():
raise LsmError(ErrorNumber.INVALID_INIT,
"No such File System: %s" % fs_id)
diff --git a/plugin/sim/simulator.py b/plugin/sim/simulator.py
index ac78950..4c0504e 100644
--- a/plugin/sim/simulator.py
+++ b/plugin/sim/simulator.py
@@ -284,7 +284,7 @@ class SimPlugin(INfs, IStorageAreaNetwork):
src_fs.id, dest_fs_name, snapshot.id, flags)

def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
if snapshot is None:
return self.sim_array.fs_file_clone(
fs.id, src_file_name, dest_file_name, None, flags)
@@ -305,7 +305,7 @@ class SimPlugin(INfs, IStorageAreaNetwork):
fs.id, snapshot.id, flags)

def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
return self.sim_array.fs_snapshot_restore(
fs.id, snapshot.id, files, restore_files, all_files, flags)

diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index d6e958b..f27a076 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -81,13 +81,14 @@ def handle_cim_errors(method):
traceback.format_exc())
return cim_wrapper

+
def _spec_ver_str_to_num(spec_ver_str):
"""
Convert version string stored in CIM_RegisteredProfile to a integer.
Example:
"1.5.1" -> 1,005,001
"""
- tmp_list = [0,0,0]
+ tmp_list = [0, 0, 0]
tmp_list = spec_ver_str.split(".")
if len(tmp_list) == 2:
tmp_list.extend([0])
@@ -97,14 +98,15 @@ def _spec_ver_str_to_num(spec_ver_str):
int(tmp_list[2]))
return None

+
class SNIA(object):
BLK_ROOT_PROFILE = 'Array'
BLK_SRVS_PROFILE = 'Block Services'
DISK_LITE_PROFILE = 'Disk Drive Lite'
MULTI_SYS_PROFILE = 'Multiple Computer System'
- SMIS_SPEC_VER_1_4 ='1.4'
- SMIS_SPEC_VER_1_5 ='1.5'
- SMIS_SPEC_VER_1_6 ='1.6'
+ SMIS_SPEC_VER_1_4 = '1.4'
+ SMIS_SPEC_VER_1_5 = '1.5'
+ SMIS_SPEC_VER_1_6 = '1.6'
REG_ORG_CODE = pywbem.Uint16(11)


@@ -579,13 +581,13 @@ class Smis(IStorageAreaNetwork):

if 'force_fallback_mode' in u['parameters'] and \
u['parameters']['force_fallback_mode'] == 'yes':
- return
+ return

# Checking profile registration support status unless
# force_fallback_mode is enabled in URI.
namespace_check_list = Smis.DMTF_INTEROP_NAMESPACES
if 'namespace' in u['parameters'] and \
- u['parameters']['namespace'] not in namespace_check_list:
+ u['parameters']['namespace'] not in namespace_check_list:
namespace_check_list.extend([u['parameters']['namespace']])

for interop_namespace in Smis.DMTF_INTEROP_NAMESPACES:
@@ -708,7 +710,7 @@ class Smis(IStorageAreaNetwork):
cap.set(Capabilities.VOLUME_REPLICATE_CLONE)

if self.RepSvc.RepTypes.SYNC_CLONE_LOCAL in s_rt or \
- self.RepSvc.RepTypes.ASYNC_CLONE_LOCAL in s_rt:
+ self.RepSvc.RepTypes.ASYNC_CLONE_LOCAL in s_rt:
cap.set(Capabilities.VOLUME_REPLICATE_COPY)
else:
# Try older storage configuration service
@@ -729,14 +731,14 @@ class Smis(IStorageAreaNetwork):
if len(sct):
cap.set(Capabilities.VOLUME_REPLICATE)

- # Mirror support is not working and is not supported at
- # this time.
+ # Mirror support is not working and is not supported at
+ # this time.

- # if Smis.CopyTypes.ASYNC in sct:
- # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_ASYNC)
+ # if Smis.CopyTypes.ASYNC in sct:
+ # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_ASYNC)

- # if Smis.CopyTypes.SYNC in sct:
- # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_SYNC)
+ # if Smis.CopyTypes.SYNC in sct:
+ # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_SYNC)

if Smis.CopyTypes.UNSYNCASSOC in sct:
cap.set(Capabilities.VOLUME_REPLICATE_CLONE)
@@ -1531,9 +1533,10 @@ class Smis(IStorageAreaNetwork):
status |= System.STATUS_OK
elif os == Smis.SystemOperationalStatus.DEGRADED:
status |= System.STATUS_DEGRADED
- elif os == Smis.SystemOperationalStatus.ERROR or \
- os == Smis.SystemOperationalStatus.STRESSED or \
- os == Smis.SystemOperationalStatus.NON_RECOVERABLE_ERROR:
+ elif (os == Smis.SystemOperationalStatus.ERROR or
+ os == Smis.SystemOperationalStatus.STRESSED or
+ os ==
+ Smis.SystemOperationalStatus.NON_RECOVERABLE_ERROR):
status |= System.STATUS_ERROR
elif os == Smis.SystemOperationalStatus.PREDICTIVE_FAILURE:
status |= System.STATUS_PREDICTIVE_FAILURE
@@ -2203,7 +2206,6 @@ class Smis(IStorageAreaNetwork):
*(self._c.InvokeMethod('HidePaths', ccs.path,
**hide_params)))[0]

-
@handle_cim_errors
def job_free(self, job_id, flags=0):
"""
@@ -2933,8 +2935,8 @@ class Smis(IStorageAreaNetwork):
cim_sys_pros = self._property_list_of_id("System")
if not self.fallback_mode and \
self._profile_is_supported(SNIA.BLK_SRVS_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False) is None:
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False) is None:
raise LsmError(ErrorNumber.NO_SUPPORT,
"SMI-S %s version %s is not supported" %
(SNIA.BLK_SRVS_PROFILE,
@@ -2964,8 +2966,8 @@ class Smis(IStorageAreaNetwork):
"""
if not self.fallback_mode and \
self._profile_is_supported(SNIA.BLK_SRVS_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False) is None:
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False) is None:
raise LsmError(ErrorNumber.NO_SUPPORT,
"SMI-S %s version %s is not supported" %
(SNIA.BLK_SRVS_PROFILE,
@@ -3096,8 +3098,8 @@ class Smis(IStorageAreaNetwork):
possible_element_names = []
if raid_type == Pool.RAID_TYPE_JBOD:
possible_element_names = ['JBOD']
- elif raid_type == Pool.RAID_TYPE_RAID0 or \
- raid_type == Pool.RAID_TYPE_NOT_APPLICABLE:
+ elif (raid_type == Pool.RAID_TYPE_RAID0 or
+ raid_type == Pool.RAID_TYPE_NOT_APPLICABLE):
possible_element_names = ['RAID0']
elif raid_type == Pool.RAID_TYPE_RAID1:
possible_element_names = ['RAID1']
@@ -3387,7 +3389,7 @@ class Smis(IStorageAreaNetwork):
# we enumerate CIM_ComputerSystem.
try:
cim_scss_path = self._c.EnumerateInstanceNames(
- 'CIM_StorageConfigurationService')
+ 'CIM_StorageConfigurationService')
except CIMError as e:
# If array does not support CIM_StorageConfigurationService
# we use CIM_ComputerSystem which is mandatory.
@@ -3444,7 +3446,6 @@ class Smis(IStorageAreaNetwork):
else:
return cim_syss

-
def _cim_sys_of_id(self, system_id, property_list=None):
"""
Return a CIMInstance of CIM_ComputerSystem for given system id.
diff --git a/python_binding/lsm/_client.py b/python_binding/lsm/_client.py
index 0201da8..9dc764a 100644
--- a/python_binding/lsm/_client.py
+++ b/python_binding/lsm/_client.py
@@ -40,17 +40,19 @@ def _del_self(d):
del d['self']
return d

+
def _check_search_key(search_key, supported_keys):
if search_key and search_key not in supported_keys:
raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
"Unsupported search_key: '%s'" % search_key)
return

+
## Descriptive exception about daemon not running.
def _raise_no_daemon():
raise LsmError(ErrorNumber.DAEMON_NOT_RUNNING,
- "The libStorageMgmt daemon is not running (process "
- "name lsmd), try 'service libstoragemgmt start'")
+ "The libStorageMgmt daemon is not running (process "
+ "name lsmd), try 'service libstoragemgmt start'")


## Main client class for library.
@@ -146,8 +148,7 @@ class Client(INetworkAttachedStorage):
#the directory for other unix domain sockets.
if Client._check_daemon_exists():
raise LsmError(ErrorNumber.PLUGIN_NOT_EXIST,
- "Plug-in " + self.plugin_path +
- " not found!")
+ "Plug-in %s not found!" % self.plugin_path)
else:
_raise_no_daemon()

@@ -517,7 +518,8 @@ class Client(INetworkAttachedStorage):
# @returns List of initiators
@_return_requires([Initiator])
def initiators_granted_to_volume(self, volume, flags=0):
- return self._tp.rpc('initiators_granted_to_volume', _del_self(locals()))
+ return self._tp.rpc('initiators_granted_to_volume',
+ _del_self(locals()))

## Returns an array of volume objects
# @param self The this pointer
@@ -777,7 +779,8 @@ class Client(INetworkAttachedStorage):
"""
Deletes an initiator from an access group
"""
- return self._tp.rpc('access_group_initiator_delete', _del_self(locals()))
+ return self._tp.rpc('access_group_initiator_delete',
+ _del_self(locals()))

## Returns the list of volumes that access group has access to.
# @param self The this pointer
@@ -936,7 +939,7 @@ class Client(INetworkAttachedStorage):
# @returns None on success, else job id
@_return_requires(unicode)
def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
"""
Creates a thinly provisioned clone of src to dest.
Note: Source and Destination are required to be on same filesystem and
@@ -1014,7 +1017,7 @@ class Client(INetworkAttachedStorage):
# @return None on success, else job id
@_return_requires(unicode)
def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
"""
WARNING: Destructive!

diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index b06c3c1..3eca617 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
@@ -54,6 +54,7 @@ def default_property(name, allow_set=True, doc=None):

return decorator

+
def common_urllib2_error_handler(exp):

if isinstance(exp, urllib2.HTTPError):
@@ -75,6 +76,7 @@ def common_urllib2_error_handler(exp):
raise LsmError(ErrorNumber.PLUGIN_ERROR, "Unexpected exception",
stack_trace)

+
## Documentation for Proxy class.
#
# Class to encapsulate the actual class we want to call. When an attempt is
diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index 60fcad8..6a612cf 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -160,6 +160,7 @@ class IData(object):
"""
return str(self._to_dict())

+
@default_property('id', doc="Unique identifier")
@default_property('type', doc="Enumerated initiator type")
@default_property('name', doc="User supplied name")
@@ -265,7 +266,7 @@ class Disk(IData):
'owner_ctrler_id']

def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
+ list_convert):
if enum_as_number is False:
if key_name == 'status':
value = self.status_to_str(value)
@@ -312,6 +313,7 @@ class Disk(IData):
def __str__(self):
return self.name

+
@default_property('id', doc="Unique identifier")
@default_property('name', doc="User given name")
@default_property('vpd83', doc="Vital product page 0x83 identifier")
@@ -660,7 +662,6 @@ class Pool(IData):
# DESTROYING:
# Array is removing current pool.

-
OPT_PROPERTIES = ['raid_type', 'member_type', 'member_ids',
'element_type', 'thinp_type']

@@ -697,6 +698,7 @@ class Pool(IData):
@default_property('system_id', doc="System ID")
class FileSystem(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
+
def __init__(self, _id, _name, _total_space, _free_space, _pool_id,
_system_id):
self._id = _id
@@ -765,6 +767,7 @@ class BlockRange(IData):
@default_property('system_id', doc="System identifier")
class AccessGroup(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
+
def __init__(self, _id, _name, _initiators, _system_id='NA'):
self._id = _id
self._name = _name # AccessGroup name
diff --git a/python_binding/lsm/_iplugin.py b/python_binding/lsm/_iplugin.py
index 1d3e783..8819c28 100644
--- a/python_binding/lsm/_iplugin.py
+++ b/python_binding/lsm/_iplugin.py
@@ -100,7 +100,8 @@ class IPlugin(object):
"""
Returns the description and version for plug-in, raises LsmError

- Note: Make sure plugin can handle this call before plugin_register is called.
+ Note: Make sure plugin can handle this call before plugin_register is
+ called.
"""
pass

@@ -498,7 +499,7 @@ class INetworkAttachedStorage(IPlugin):
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
"""
WARNING: Destructive!

diff --git a/python_binding/lsm/plugin_helper/plugin_helper.py b/python_binding/lsm/plugin_helper/plugin_helper.py
index fe270bc..33fa084 100644
--- a/python_binding/lsm/plugin_helper/plugin_helper.py
+++ b/python_binding/lsm/plugin_helper/plugin_helper.py
@@ -38,6 +38,7 @@ _QUERY_METHOD = {
NfsExport: 'exports',
}

+
def _search_property(plugin_self, class_obj, search_key, search_value, flags):
"""
This method does not check whether lsm_obj contain requested property.
@@ -60,30 +61,37 @@ def _search_property(plugin_self, class_obj, search_key, search_value, flags):
return list(lsm_obj for lsm_obj in lsm_objs
if getattr(lsm_obj, search_key) == search_value)

+
def system_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, System, search_key, search_value, flags)

+
def pool_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, Pool, search_key, search_value, flags)

+
def volume_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, Volume, search_key, search_value, flags)

+
def disk_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, Disk, search_key, search_value, flags)

+
def access_group_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, AccessGroup, search_key, search_value, flags)

+
def fs_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, FileSystem, search_key, search_value, flags)

+
def nfs_export_search(plugin_self, search_key, search_value, flags=0):
return _search_property(
plugin_self, NfsExport, search_key, search_value, flags)
--
1.8.3.1
Gris Ge
2014-06-02 10:13:43 UTC
Permalink
* Add search support to these methods:
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)

* Add new capabilities:
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH

* Add new class constant:
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS

* New ErrorNumber:
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY

* New public method:
lsm.search_property(search_key, search_value)
# This is for plugin only.
# It could be much better if we let _pluginrunner.py handle the
# general searching in stead of plugin itself. But that will waive out
# the plugin standalone mode. We can discuss this later.

* lsmcli now allowing '--sys' and etc options in list command for searching.

* Tests for lsmcli about query with search.

Changes since V3:
* Not search support for systems().
* Not using lsm.plug_helper module for general search.

Gris Ge (4):
Python library: add search support to query methods
lsmcli: add search support for list command
Plugin: add search support to query methods
Test: Add search test to cmdtest.py

doc/man/lsmcli.1.in | 29 ++++++++
plugin/nstor/nstor.py | 30 ++++-----
plugin/ontap/ontap.py | 44 +++++++-----
plugin/sim/simulator.py | 50 +++++++++-----
plugin/smispy/smis.py | 72 ++++++++++----------
plugin/targetd/targetd.py | 22 +++---
plugin/v7k/ibmv7k.py | 15 +++--
python_binding/lsm/__init__.py | 2 +-
python_binding/lsm/_client.py | 44 +++++++-----
python_binding/lsm/_common.py | 4 ++
python_binding/lsm/_data.py | 21 +++++-
python_binding/lsm/_iplugin.py | 10 +--
python_binding/lsm/_pluginrunner.py | 11 +++
test/cmdtest.py | 50 ++++++++++++++
tools/lsmcli/cmdline.py | 129 +++++++++++++++++++++++++++++++-----
15 files changed, 395 insertions(+), 138 deletions(-)
--
1.8.3.1
Gris Ge
2014-06-02 10:13:44 UTC
Permalink
* Add search support to these methods:
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)

* Add new capabilities:
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH

* Add new class constant:
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS

* New ErrorNumber:
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY

* New public method:
lsm.search_property(search_key, search_value)
# This is for plugin only.
# It could be much better if we let _pluginrunner.py handle the
# general searching in stead of plugin itself. But that will waive out
# the plugin standalone mode. We can discuss this later.

Signed-off-by: Gris Ge <***@redhat.com>
---
python_binding/lsm/__init__.py | 2 +-
python_binding/lsm/_client.py | 44 ++++++++++++++++++++++++-------------
python_binding/lsm/_common.py | 4 ++++
python_binding/lsm/_data.py | 21 +++++++++++++++---
python_binding/lsm/_iplugin.py | 10 ++++-----
python_binding/lsm/_pluginrunner.py | 11 ++++++++++
6 files changed, 68 insertions(+), 24 deletions(-)

diff --git a/python_binding/lsm/__init__.py b/python_binding/lsm/__init__.py
index c522d4b..6427020 100644
--- a/python_binding/lsm/__init__.py
+++ b/python_binding/lsm/__init__.py
@@ -12,4 +12,4 @@ from _iplugin import IPlugin, IStorageAreaNetwork, INetworkAttachedStorage, \
INfs

from _client import Client
-from _pluginrunner import PluginRunner
+from _pluginrunner import PluginRunner, search_property
diff --git a/python_binding/lsm/_client.py b/python_binding/lsm/_client.py
index 3cf6087..233c990 100644
--- a/python_binding/lsm/_client.py
+++ b/python_binding/lsm/_client.py
@@ -21,7 +21,7 @@ import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, NfsExport)

from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
@@ -41,11 +41,18 @@ def _del_self(d):
return d


+def _check_search_key(search_key, supported_keys):
+ if search_key and search_key not in supported_keys:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search_key: '%s'" % search_key)
+ return
+
+
## Descriptive exception about daemon not running.
def _raise_no_daemon():
raise LsmError(ErrorNumber.DAEMON_NOT_RUNNING,
- "The libStorageMgmt daemon is not running (process "
- "name lsmd), try 'service libstoragemgmt start'")
+ "The libStorageMgmt daemon is not running (process "
+ "name lsmd), try 'service libstoragemgmt start'")


## Main client class for library.
@@ -141,8 +148,7 @@ class Client(INetworkAttachedStorage):
#the directory for other unix domain sockets.
if Client._check_daemon_exists():
raise LsmError(ErrorNumber.PLUGIN_NOT_EXIST,
- "Plug-in " + self.plugin_path +
- " not found!")
+ "Plug-in %s not found!" % self.plugin_path)
else:
_raise_no_daemon()

@@ -284,11 +290,12 @@ class Client(INetworkAttachedStorage):
# returned.
# @returns An array of pool objects.
@_return_requires([Pool])
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of pool objects. Pools are used in both block and
file system interfaces, thus the reason they are in the base class.
"""
+ _check_search_key(search_key, Pool.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('pools', _del_self(locals()))

## Create new pool in user friendly way. Depending on this capability:
@@ -510,17 +517,19 @@ class Client(INetworkAttachedStorage):
# @returns List of initiators
@_return_requires([Initiator])
def initiators_granted_to_volume(self, volume, flags=0):
- return self._tp.rpc('initiators_granted_to_volume', _del_self(locals()))
+ return self._tp.rpc('initiators_granted_to_volume',
+ _del_self(locals()))

## Returns an array of volume objects
# @param self The this pointer
# @param flags Reserved for future use, must be zero.
# @returns An array of volume objects.
@_return_requires([Volume])
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects
"""
+ _check_search_key(search_key, Volume.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('volumes', _del_self(locals()))

## Creates a volume
@@ -668,10 +677,11 @@ class Client(INetworkAttachedStorage):
# be returned.
# @returns An array of disk objects.
@_return_requires([Disk])
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of disk objects
"""
+ _check_search_key(search_key, Disk.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('disks', _del_self(locals()))

## Access control for allowing an access group to access a volume
@@ -706,10 +716,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns List of access groups
@_return_requires([AccessGroup])
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups
"""
+ _check_search_key(search_key, AccessGroup.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('access_groups', _del_self(locals()))

## Creates an access a group with the specified initiator in it.
@@ -767,7 +778,8 @@ class Client(INetworkAttachedStorage):
"""
Deletes an initiator from an access group
"""
- return self._tp.rpc('access_group_initiator_delete', _del_self(locals()))
+ return self._tp.rpc('access_group_initiator_delete',
+ _del_self(locals()))

## Returns the list of volumes that access group has access to.
# @param self The this pointer
@@ -836,10 +848,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns A list of FS objects.
@_return_requires([FileSystem])
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of file systems on the controller.
"""
+ _check_search_key(search_key, FileSystem.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('fs', _del_self(locals()))

## Deletes a file system
@@ -925,7 +938,7 @@ class Client(INetworkAttachedStorage):
# @returns None on success, else job id
@_return_requires(unicode)
def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
"""
Creates a thinly provisioned clone of src to dest.
Note: Source and Destination are required to be on same filesystem and
@@ -1003,7 +1016,7 @@ class Client(INetworkAttachedStorage):
# @return None on success, else job id
@_return_requires(unicode)
def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
"""
WARNING: Destructive!

@@ -1071,10 +1084,11 @@ class Client(INetworkAttachedStorage):
# @param flags Reserved for future use, must be zero.
# @returns An array of export objects
@_return_requires([NfsExport])
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
+ _check_search_key(search_key, NfsExport.SUPPORTED_SEARCH_KEYS)
return self._tp.rpc('exports', _del_self(locals()))

## Exports a FS as specified in the export.
diff --git a/python_binding/lsm/_common.py b/python_binding/lsm/_common.py
index 0ba2965..3eca617 100644
--- a/python_binding/lsm/_common.py
+++ b/python_binding/lsm/_common.py
@@ -54,6 +54,7 @@ def default_property(name, allow_set=True, doc=None):

return decorator

+
def common_urllib2_error_handler(exp):

if isinstance(exp, urllib2.HTTPError):
@@ -75,6 +76,7 @@ def common_urllib2_error_handler(exp):
raise LsmError(ErrorNumber.PLUGIN_ERROR, "Unexpected exception",
stack_trace)

+
## Documentation for Proxy class.
#
# Class to encapsulate the actual class we want to call. When an attempt is
@@ -516,6 +518,8 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ UNSUPPORTED_SEARCH_KEY = 510
+
_LOCALS = locals()

@staticmethod
diff --git a/python_binding/lsm/_data.py b/python_binding/lsm/_data.py
index c72dc54..9f2f5db 100644
--- a/python_binding/lsm/_data.py
+++ b/python_binding/lsm/_data.py
@@ -159,6 +159,7 @@ class IData(object):
"""
return str(self._to_dict())

+
@default_property('id', doc="Unique identifier")
@default_property('type', doc="Enumerated initiator type")
@default_property('name', doc="User supplied name")
@@ -191,6 +192,7 @@ class Disk(IData):
"""
Represents a disk.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
RETRIEVE_FULL_INFO = 2 # Used by _client.py for disks() call.

# We use '-1' to indicate we failed to get the requested number.
@@ -263,7 +265,7 @@ class Disk(IData):
'owner_ctrler_id']

def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
+ list_convert):
if enum_as_number is False:
if key_name == 'status':
value = self.status_to_str(value)
@@ -310,6 +312,7 @@ class Disk(IData):
def __str__(self):
return self.name

+
@default_property('id', doc="Unique identifier")
@default_property('name', doc="User given name")
@default_property('vpd83', doc="Vital product page 0x83 identifier")
@@ -322,6 +325,7 @@ class Volume(IData):
"""
Represents a volume.
"""
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
# Volume status Note: Volumes can have multiple status bits set at same
# time.
(STATUS_UNKNOWN, STATUS_OK, STATUS_DEGRADED, STATUS_ERR, STATUS_STARTING,
@@ -440,7 +444,6 @@ The lsm.System class does not have any extra constants.

The lsm.System class does not have class methods.
"""
-
STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
STATUS_ERROR = 1 << 2
@@ -474,6 +477,7 @@ class Pool(IData):
RETRIEVE_FULL_INFO = 1 # Used by _client.py for pools() call.
# This might not be a good place, please
# suggest a better one.
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

TOTAL_SPACE_NOT_FOUND = -1
FREE_SPACE_NOT_FOUND = -1
@@ -655,7 +659,6 @@ class Pool(IData):
# DESTROYING:
# Array is removing current pool.

-
OPT_PROPERTIES = ['raid_type', 'member_type', 'member_ids',
'element_type', 'thinp_type']

@@ -691,6 +694,8 @@ class Pool(IData):
@default_property('pool_id', doc="What pool the file system resides on")
@default_property('system_id', doc="System ID")
class FileSystem(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
+
def __init__(self, _id, _name, _total_space, _free_space, _pool_id,
_system_id):
self._id = _id
@@ -722,6 +727,7 @@ class FsSnapshot(IData):
@default_property('anongid', doc="GID for anonymous group id")
@default_property('options', doc="String containing advanced options")
class NfsExport(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'fs_id']
ANON_UID_GID_NA = -1
ANON_UID_GID_ERROR = (ANON_UID_GID_NA - 1)

@@ -757,6 +763,8 @@ class BlockRange(IData):
@default_property('initiators', doc="List of initiators")
@default_property('system_id', doc="System identifier")
class AccessGroup(IData):
+ SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
+
def __init__(self, _id, _name, _initiators, _system_id='NA'):
self._id = _id
self._name = _name # AccessGroup name
@@ -907,6 +915,13 @@ class Capabilities(IData):

POOL_DELETE = 200

+ POOLS_QUICK_SEARCH = 210
+ VOLUMES_QUICK_SEARCH = 211
+ DISKS_QUICK_SEARCH = 212
+ ACCESS_GROUPS_QUICK_SEARCH = 213
+ FS_QUICK_SEARCH = 214
+ NFS_EXPORTS_QUICK_SEARCH = 215
+
def _to_dict(self):
rc = {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
diff --git a/python_binding/lsm/_iplugin.py b/python_binding/lsm/_iplugin.py
index 1d3e783..3368293 100644
--- a/python_binding/lsm/_iplugin.py
+++ b/python_binding/lsm/_iplugin.py
@@ -105,7 +105,7 @@ class IPlugin(object):
pass

@_abstractmethod
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of pool objects. Pools are used in both block and
file system interfaces, thus the reason they are in the base class.
@@ -173,7 +173,7 @@ class IStorageAreaNetwork(IPlugin):
"""
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects

@@ -315,7 +315,7 @@ class IStorageAreaNetwork(IPlugin):
"""
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups, raises LsmError on errors.
"""
@@ -402,7 +402,7 @@ class INetworkAttachedStorage(IPlugin):
"""
Class the represents Network attached storage (Common NFS/CIFS operations)
"""
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of file systems on the controller. Raises LsmError on
errors.
@@ -546,7 +546,7 @@ class INfs(INetworkAttachedStorage):
"""
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
diff --git a/python_binding/lsm/_pluginrunner.py b/python_binding/lsm/_pluginrunner.py
index de2c832..e4a2552 100644
--- a/python_binding/lsm/_pluginrunner.py
+++ b/python_binding/lsm/_pluginrunner.py
@@ -24,6 +24,17 @@ import _transport
from lsm.lsmcli import cmd_line_wrapper


+def search_property(lsm_objs, search_key, search_value):
+ """
+ This method does not check whether lsm_obj contain requested property.
+ The method caller should do the check.
+ """
+ if search_key is None:
+ return lsm_objs
+ return list(lsm_obj for lsm_obj in lsm_objs
+ if getattr(lsm_obj, search_key) == search_value)
+
+
class PluginRunner(object):
"""
Plug-in side common code which uses the passed in plugin to do meaningful
--
1.8.3.1
Gris Ge
2014-06-02 10:13:45 UTC
Permalink
* Add support of query search into lsmcli 'list' command for these options:
--sys
--pool
--vol
--disk
--ag
--fs
--nfs-export
* Manpage updated with detail support status.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 29 +++++++++++
tools/lsmcli/cmdline.py | 129 ++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 143 insertions(+), 15 deletions(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..dc79ad0 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -161,6 +161,35 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
available.
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Search resources from system with SYS_ID. Only supported when querying these
+types of resources: \fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR,
+\fBSNAPSHOTS\fR, \fBDISKS\fR, \fBACCESS_GROUPS\fR.
+.TP
+\fB--pool\fR \fI<POOL_ID>\fR
+Search resources from pool with POOL_ID. Only supported by these types of
+resources: \fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Search resources from volume with VOL_ID. Only supported by these types of
+resources: \fBVOLUMES\fR.
+.TP
+\fB--disk\fR \fI<DISK_ID>\fR
+Search resources from disk with DISK_ID. Only supported by these types of
+resources: \fBDISK\fR.
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Search resources from access group with AG_ID. Only supported by these types
+of resources: \fBACCESS_GROUPS\fR.
+.TP
+\fB--fs\fR \fI<FS_ID>\fR
+Search resources from file system with FS_ID. Only supported by these types
+of resources: \fBFS\fR.
+.TP
+\fB--nfs-export\fR \fI<NFS_EXPORT_ID>\fR
+Search resources from NFS export with NFS_EXPORT_ID. Only supported by these
+types of resources: \fBEXPORTS\fR.

.SS job-status
Retrieve information about a job.
diff --git a/tools/lsmcli/cmdline.py b/tools/lsmcli/cmdline.py
index 9cc0362..3870b5d 100644
--- a/tools/lsmcli/cmdline.py
+++ b/tools/lsmcli/cmdline.py
@@ -28,7 +28,8 @@ from argparse import RawTextHelpFormatter

from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse, Proxy, size_human_2_size_bytes)
+ uri_parse, Proxy, size_human_2_size_bytes, System,
+ AccessGroup, FileSystem, NfsExport)

from lsm.lsmcli.data_display import (
DisplayData, PlugData, out,
@@ -138,14 +139,34 @@ for i in range(0, len(raid_types), 4):
raid_help = "Valid RAID type:" + raid_types_formatted

sys_id_opt = dict(name='--sys', metavar='<SYS_ID>', help='System ID')
+sys_id_filter_opt = sys_id_opt
+sys_id_filter_opt['help'] = 'Search by System ID'
+
pool_id_opt = dict(name='--pool', metavar='<POOL_ID>', help='Pool ID')
+pool_id_filter_opt = pool_id_opt
+pool_id_filter_opt['help'] = 'Search by Pool ID'
+
vol_id_opt = dict(name='--vol', metavar='<VOL_ID>', help='Volume ID')
-fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File system ID')
-ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access group ID')
+vol_id_filter_opt = vol_id_opt
+vol_id_filter_opt['help'] = 'Search by Volume ID'
+
+fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File System ID')
+
+ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access Group ID')
+ag_id_filter_opt = ag_id_opt
+ag_id_filter_opt['help'] = 'Search by Access Group ID'
+
init_id_opt = dict(name='--init', metavar='<INIT_ID>', help='Initiator ID')
snap_id_opt = dict(name='--snap', metavar='<SNAP_ID>', help='Snapshot ID')
export_id_opt = dict(name='--export', metavar='<EXPORT_ID>', help='Export ID')

+nfs_export_id_filter_opt = dict(
+ name='--nfs-export', metavar='<NFS_EXPORT_ID>',
+ help='Search by NFS Export ID')
+
+disk_id_filter_opt = dict(name='--disk', metavar='<DISK_ID>',
+ help='Search by Disk ID')
+
size_opt = dict(name='--size', metavar='<SIZE>', help=size_help)
access_opt = dict(name='--access', metavar='<ACCESS>', help=access_help,
choices=access_types, type=str.upper)
@@ -174,7 +195,13 @@ cmds = (
default=False,
dest='all',
action='store_true'),
+ dict(sys_id_filter_opt),
+ dict(pool_id_filter_opt),
+ dict(vol_id_filter_opt),
+ dict(disk_id_filter_opt),
+ dict(ag_id_filter_opt),
dict(fs_id_opt),
+ dict(nfs_export_id_filter_opt),
],
),

@@ -890,16 +917,56 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.sys:
+ search_key = 'system_id'
+ search_value = args.sys
+ if args.pool:
+ search_key = 'pool_id'
+ search_value = args.pool
+ if args.vol:
+ search_key = 'volume_id'
+ search_value = args.vol
+ if args.disk:
+ search_key = 'disk_id'
+ search_value = args.disk
+ if args.ag:
+ search_key = 'access_group_id'
+ search_value = args.ag
+ if args.fs:
+ search_key = 'fs_id'
+ search_value = args.ag
+ if args.nfs_export:
+ search_key = 'nfs_export_id'
+ search_value = args.nfs_export
+
if args.type == 'VOLUMES':
- self.display_data(self.c.volumes())
+ if search_key == 'volume_id':
+ search_key = 'id'
+ if search_key and search_key not in Volume.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "volume listing." % search_key)
+ self.display_data(self.c.volumes(search_key, search_value))
elif args.type == 'POOLS':
+ if search_key == 'pool_id':
+ search_key = 'id'
+ if search_key and search_key not in Pool.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "pool listing." % search_key)
+ flags = 0
if args.all:
- self.display_data(
- self.c.pools(Pool.RETRIEVE_FULL_INFO))
- else:
- self.display_data(self.c.pools())
+ flags |= Pool.RETRIEVE_FULL_INFO
+ self.display_data(
+ self.c.pools(search_key, search_value, flags))
elif args.type == 'FS':
- self.display_data(self.c.fs())
+ if search_key == 'fs_id':
+ search_key = 'id'
+ if search_key and \
+ search_key not in FileSystem.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "volume listing." % search_key)
+ self.display_data(self.c.fs(search_key, search_value))
elif args.type == 'SNAPSHOTS':
if args.fs is None:
raise ArgError("--fs <file system id> required")
@@ -909,19 +976,39 @@ class CmdLine:
elif args.type == 'INITIATORS':
self.display_data(self.c.initiators())
elif args.type == 'EXPORTS':
- self.display_data(self.c.exports())
+ if search_key == 'nfs_export_id':
+ search_key = 'id'
+ if search_key and \
+ search_key not in NfsExport.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "NFS Export listing" % search_key)
+ self.display_data(self.c.exports(search_key, search_value))
elif args.type == 'NFS_CLIENT_AUTH':
self.display_nfs_client_authentication()
elif args.type == 'ACCESS_GROUPS':
- self.display_data(self.c.access_groups())
+ if search_key == 'access_group_id':
+ search_key = 'id'
+ if 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(
+ self.c.access_groups(search_key,search_value))
elif args.type == 'SYSTEMS':
+ if search_key:
+ raise ArgError("System listing with search is not supported")
self.display_data(self.c.systems())
elif args.type == 'DISKS':
+ if search_key == 'disk_id':
+ search_key = 'id'
+ if search_key and search_key not in Disk.SUPPORTED_SEARCH_KEYS:
+ raise ArgError("Search key '%s' is not supported by "
+ "disk listing" % search_key)
+ flags = 0
if args.all:
- self.display_data(
- self.c.disks(Disk.RETRIEVE_FULL_INFO))
- else:
- self.display_data(self.c.disks())
+ flags |= Disk.RETRIEVE_FULL_INFO
+ self.display_data(
+ self.c.disks(search_key, search_value, flags))
elif args.type == 'PLUGINS':
self.display_available_plugins()
else:
@@ -1178,6 +1265,18 @@ class CmdLine:
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))

def plugin_info(self, args):
desc, version = self.c.plugin_info()
--
1.8.3.1
Gris Ge
2014-06-02 10:13:46 UTC
Permalink
* Plugins update for search support of query methods.
* Some PEP8 clean up.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/nstor/nstor.py | 30 ++++++++++----------
plugin/ontap/ontap.py | 44 +++++++++++++++++------------
plugin/sim/simulator.py | 50 +++++++++++++++++++++-----------
plugin/smispy/smis.py | 72 +++++++++++++++++++++++++----------------------
plugin/targetd/targetd.py | 22 +++++++--------
plugin/v7k/ibmv7k.py | 15 ++++++----
6 files changed, 134 insertions(+), 99 deletions(-)

diff --git a/plugin/nstor/nstor.py b/plugin/nstor/nstor.py
index c661090..06529c6 100644
--- a/plugin/nstor/nstor.py
+++ b/plugin/nstor/nstor.py
@@ -30,7 +30,7 @@ import time
from lsm import (AccessGroup, Capabilities, ErrorNumber, FileSystem, INfs,
IStorageAreaNetwork, Initiator, LsmError, NfsExport, Pool,
FsSnapshot, System, VERSION, Volume, md5,
- common_urllib2_error_handler)
+ common_urllib2_error_handler, search_property)


class NexentaStor(INfs, IStorageAreaNetwork):
@@ -104,7 +104,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
float(size[:-1]) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return size

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
pools_list = self._request("get_all_names", "volume", [""])

pools = []
@@ -120,9 +120,9 @@ class NexentaStor(INfs, IStorageAreaNetwork):
Pool.STATUS_UNKNOWN, '',
self.system.id))

- return pools
+ return search_property(pools, search_key, search_value)

- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
fs_list = self._request("get_all_names", "folder", [""])

fss = []
@@ -143,7 +143,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
pool_name,
fs))

- return fss
+ return search_property(fss, search_key, search_value)

def fs_create(self, pool, name, size_bytes, flags=0):
"""
@@ -171,7 +171,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
snapshot_info = self._request("get_child_props", "snapshot",
[snapshot, "creation_seconds"])
snapshots.append(FsSnapshot(snapshot, snapshot,
- snapshot_info['creation_seconds']))
+ snapshot_info['creation_seconds']))

return snapshots

@@ -182,7 +182,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
snapshot_info = self._request("get_child_props", "snapshot",
[full_name, "creation_seconds"])
return None, FsSnapshot(full_name, full_name,
- snapshot_info['creation_seconds'])
+ snapshot_info['creation_seconds'])

def fs_snapshot_delete(self, fs, snapshot, flags=0):
self._request("destroy", "snapshot", [snapshot.name, ""])
@@ -302,11 +302,11 @@ class NexentaStor(INfs, IStorageAreaNetwork):
return None, fs

def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
return

def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
self._request("rollback", "snapshot", [snapshot.name, '-r'])
return

@@ -350,7 +350,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
rc.append(m.split('=>')[0])
return rc

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
"""
Get a list of all exported file systems on the controller.
"""
@@ -368,7 +368,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
'N/A', 'N/A',
opts['extra_options']))

- return exports
+ return search_property(exports, search_key, search_value)

def export_fs(self, fs_id, export_path, root_list, rw_list, ro_list,
anon_uid, anon_gid, auth_type, options, flags=0):
@@ -417,7 +417,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
def _calc_group(name):
return 'lsm_' + md5(name)[0:8]

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Returns an array of volume objects

@@ -464,7 +464,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
'N/A',
NexentaStor._get_pool_id(lu)))

- return vol_list
+ return search_property(vol_list, search_key, search_value)

def initiators(self, flags=0):
"""
@@ -693,7 +693,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
[volume.name, view_number])
return

- def access_group(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
"""
Returns a list of access groups
"""
@@ -705,7 +705,7 @@ class NexentaStor(INfs, IStorageAreaNetwork):
[hg])

ag_list.append(AccessGroup(hg, hg, initiators, self.system.id))
- return ag_list
+ return search_property(ag_list, search_key, search_value)

def access_group_create(self, name, initiator_id, id_type, system_id,
flags=0):
diff --git a/plugin/ontap/ontap.py b/plugin/ontap/ontap.py
index 5e9aa12..9ddb39d 100644
--- a/plugin/ontap/ontap.py
+++ b/plugin/ontap/ontap.py
@@ -15,6 +15,7 @@
# USA
#
# Author: tasleson
+# Gris Ge <***@redhat.com>

import os
import traceback
@@ -25,7 +26,8 @@ import na
from lsm import (Volume, Initiator, FileSystem, FsSnapshot, NfsExport,
AccessGroup, System, Capabilities, Disk, Pool, OptionalData,
IStorageAreaNetwork, INfs, LsmError, ErrorNumber, JobStatus,
- md5, Error, VERSION, common_urllib2_error_handler)
+ md5, Error, VERSION, common_urllib2_error_handler,
+ search_property)

#Maps na to lsm, this is expected to expand over time.
e_map = {
@@ -202,7 +204,7 @@ class Ontap(IStorageAreaNetwork, INfs):
#If we use the newer API we can use the uuid instead of this fake
#md5 one
return FsSnapshot(md5(s['name'] + s['access-time']), s['name'],
- s['access-time'])
+ s['access-time'])

@staticmethod
def _disk_type(netapp_disk_type):
@@ -287,9 +289,10 @@ class Ontap(IStorageAreaNetwork, INfs):
self.sys_info.id, opt_data)

@handle_ontap_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
luns = self.f.luns_get_all()
- return [self._lun(l) for l in luns]
+ return search_property(
+ [self._lun(l) for l in luns], search_key, search_value)

@staticmethod
def _pool_id(na_xxx):
@@ -471,12 +474,13 @@ class Ontap(IStorageAreaNetwork, INfs):
return "NetApp Filer support", VERSION

@handle_ontap_errors
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
disks = self.f.disks()
- return [self._disk(d, flags) for d in disks]
+ return search_property(
+ [self._disk(d, flags) for d in disks], search_key, search_value)

@handle_ontap_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
pools = []
na_aggrs = self.f.aggregates()
na_disks = []
@@ -489,7 +493,7 @@ class Ontap(IStorageAreaNetwork, INfs):
na_vols = self.f.volumes()
for na_vol in na_vols:
pools.extend([self._pool_from_na_vol(na_vol, na_aggrs, flags)])
- return pools
+ return search_property(pools, search_key, search_value)

@handle_ontap_errors
def systems(self, flags=0):
@@ -742,9 +746,10 @@ class Ontap(IStorageAreaNetwork, INfs):
self.sys_info.id)

@handle_ontap_errors
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
groups = self.f.igroups()
- return [self._access_group(g) for g in groups]
+ return search_property(
+ [self._access_group(g) for g in groups], search_key, search_value)

@handle_ontap_errors
def access_group_create(self, name, initiator_id, id_type, system_id,
@@ -863,10 +868,11 @@ class Ontap(IStorageAreaNetwork, INfs):
return None

@handle_ontap_errors
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
volumes = self.f.volumes()
pools = self.pools()
- return [self._vol(v, pools) for v in volumes]
+ return search_property(
+ [self._vol(v, pools) for v in volumes], search_key, search_value)

@handle_ontap_errors
def fs_delete(self, fs, flags=0):
@@ -896,7 +902,7 @@ class Ontap(IStorageAreaNetwork, INfs):

@handle_ontap_errors
def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
full_src = Ontap.build_name(fs.name, src_file_name)
full_dest = Ontap.build_name(fs.name, dest_file_name)

@@ -923,7 +929,7 @@ class Ontap(IStorageAreaNetwork, INfs):
self.f.snapshot_delete(fs.name, snapshot.name)

def _ss_restore_files(self, volume_name, snapshot_name, files,
- restore_files):
+ restore_files):
for i in range(len(files)):
src = Ontap.build_name(volume_name, files[i])
dest = None
@@ -933,7 +939,7 @@ class Ontap(IStorageAreaNetwork, INfs):

@handle_ontap_errors
def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
"""
Restores a FS or files on a FS.
Note: Restoring an individual file is a O(n) operation, i.e. time it
@@ -949,7 +955,7 @@ class Ontap(IStorageAreaNetwork, INfs):
"num files != num restore_files")

self._ss_restore_files(fs.name, snapshot.name, files,
- restore_files)
+ restore_files)
return "%s@%d" % (Ontap.SS_JOB, len(files))
else:
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
@@ -1019,11 +1025,13 @@ class Ontap(IStorageAreaNetwork, INfs):
None)

@handle_ontap_errors
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
#Get the file systems once and pass to _export which needs to lookup
#the file system id by name.
v = self.fs()
- return [Ontap._export(v, e) for e in self.f.nfs_exports()]
+ return search_property(
+ [Ontap._export(v, e) for e in self.f.nfs_exports()],
+ search_key, search_value)

def _get_volume_from_id(self, fs_id):
fs = self.fs()
diff --git a/plugin/sim/simulator.py b/plugin/sim/simulator.py
index 22ddfe3..d8a80bc 100644
--- a/plugin/sim/simulator.py
+++ b/plugin/sim/simulator.py
@@ -16,11 +16,10 @@
# Author: tasleson

from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,
- IStorageAreaNetwork, Error)
+ IStorageAreaNetwork, Error, search_property)

from simarray import SimArray

-
class SimPlugin(INfs, IStorageAreaNetwork):
"""
Simple class that implements enough to allow the framework to be exercised.
@@ -76,6 +75,13 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
rc = Capabilities()
rc.enable_all()
+ rc.set(Capabilities.POOLS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.VOLUMES_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.DISKS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.FS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.ACCESS_GROUPS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.NFS_EXPORTS_QUICK_SEARCH, Capabilities.UNSUPPORTED)
return rc

def plugin_info(self, flags=0):
@@ -85,9 +91,11 @@ class SimPlugin(INfs, IStorageAreaNetwork):
sim_syss = self.sim_array.systems()
return [SimPlugin._sim_data_2_lsm(s) for s in sim_syss]

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
sim_pools = self.sim_array.pools(flags)
- return [SimPlugin._sim_data_2_lsm(p) for p in sim_pools]
+ return search_property(
+ [SimPlugin._sim_data_2_lsm(p) for p in sim_pools],
+ search_key, search_value)

def pool_create(self, system, pool_name, size_bytes,
raid_type=Pool.RAID_TYPE_UNKNOWN,
@@ -115,13 +123,17 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def pool_delete(self, pool, flags=0):
return self.sim_array.pool_delete(pool.id, flags)

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
sim_vols = self.sim_array.volumes()
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
+ return search_property(
+ [SimPlugin._sim_data_2_lsm(v) for v in sim_vols],
+ search_key, search_value)

- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
sim_disks = self.sim_array.disks()
- return [SimPlugin._sim_data_2_lsm(d) for d in sim_disks]
+ return search_property(
+ [SimPlugin._sim_data_2_lsm(d) for d in sim_disks],
+ search_key, search_value)

def volume_create(self, pool, volume_name, size_bytes, provisioning,
flags=0):
@@ -162,9 +174,11 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return self.sim_array.volume_online(volume.id, flags)

- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
sim_ags = self.sim_array.ags()
- return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ return search_property(
+ [SimPlugin._sim_data_2_lsm(a) for a in sim_ags],
+ search_key, search_value)

def access_group_create(self, name, initiator_id, id_type, system_id,
flags=0):
@@ -235,9 +249,11 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_child_dependency_rm(self, volume, flags=0):
return self.sim_array.volume_child_dependency_rm(volume.id, flags)

- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
sim_fss = self.sim_array.fs()
- return [SimPlugin._sim_data_2_lsm(f) for f in sim_fss]
+ return search_property(
+ [SimPlugin._sim_data_2_lsm(f) for f in sim_fss],
+ search_key, search_value)

def fs_create(self, pool, name, size_bytes, flags=0):
sim_fs = self.sim_array.fs_create(pool.id, name, size_bytes)
@@ -259,7 +275,7 @@ class SimPlugin(INfs, IStorageAreaNetwork):
src_fs.id, dest_fs_name, snapshot.id, flags)

def fs_file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
- flags=0):
+ flags=0):
if snapshot is None:
return self.sim_array.fs_file_clone(
fs.id, src_file_name, dest_file_name, None, flags)
@@ -280,7 +296,7 @@ class SimPlugin(INfs, IStorageAreaNetwork):
fs.id, snapshot.id, flags)

def fs_snapshot_restore(self, fs, snapshot, files, restore_files,
- all_files=False, flags=0):
+ all_files=False, flags=0):
return self.sim_array.fs_snapshot_restore(
fs.id, snapshot.id, files, restore_files, all_files, flags)

@@ -294,9 +310,11 @@ class SimPlugin(INfs, IStorageAreaNetwork):
# The API should change some day
return ["simple"]

- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
sim_exps = self.sim_array.exports(flags)
- return [SimPlugin._sim_data_2_lsm(e) for e in sim_exps]
+ return search_property(
+ [SimPlugin._sim_data_2_lsm(e) for e in sim_exps],
+ search_key, search_value)

def export_fs(self, fs_id, export_path, root_list, rw_list, ro_list,
anon_uid, anon_gid, auth_type, options, flags=0):
diff --git a/plugin/smispy/smis.py b/plugin/smispy/smis.py
index 8bcf6b3..44d2018 100644
--- a/plugin/smispy/smis.py
+++ b/plugin/smispy/smis.py
@@ -26,7 +26,8 @@ from pywbem import CIMError

from lsm import (IStorageAreaNetwork, Error, uri_parse, LsmError, ErrorNumber,
JobStatus, md5, Pool, Initiator, Volume, AccessGroup, System,
- Capabilities, Disk, OptionalData, txt_a, VERSION)
+ Capabilities, Disk, OptionalData, txt_a, VERSION,
+ search_property)

## Variable Naming scheme:
# cim_xxx CIMInstance
@@ -79,13 +80,14 @@ def handle_cim_errors(method):
traceback.format_exc())
return cim_wrapper

+
def _spec_ver_str_to_num(spec_ver_str):
"""
Convert version string stored in CIM_RegisteredProfile to a integer.
Example:
"1.5.1" -> 1,005,001
"""
- tmp_list = [0,0,0]
+ tmp_list = [0, 0, 0]
tmp_list = spec_ver_str.split(".")
if len(tmp_list) == 2:
tmp_list.extend([0])
@@ -95,14 +97,15 @@ def _spec_ver_str_to_num(spec_ver_str):
int(tmp_list[2]))
return None

+
class SNIA(object):
BLK_ROOT_PROFILE = 'Array'
BLK_SRVS_PROFILE = 'Block Services'
DISK_LITE_PROFILE = 'Disk Drive Lite'
MULTI_SYS_PROFILE = 'Multiple Computer System'
- SMIS_SPEC_VER_1_4 ='1.4'
- SMIS_SPEC_VER_1_5 ='1.5'
- SMIS_SPEC_VER_1_6 ='1.6'
+ SMIS_SPEC_VER_1_4 = '1.4'
+ SMIS_SPEC_VER_1_5 = '1.5'
+ SMIS_SPEC_VER_1_6 = '1.6'
REG_ORG_CODE = pywbem.Uint16(11)


@@ -577,13 +580,13 @@ class Smis(IStorageAreaNetwork):

if 'force_fallback_mode' in u['parameters'] and \
u['parameters']['force_fallback_mode'] == 'yes':
- return
+ return

# Checking profile registration support status unless
# force_fallback_mode is enabled in URI.
namespace_check_list = Smis.DMTF_INTEROP_NAMESPACES
if 'namespace' in u['parameters'] and \
- u['parameters']['namespace'] not in namespace_check_list:
+ u['parameters']['namespace'] not in namespace_check_list:
namespace_check_list.extend([u['parameters']['namespace']])

for interop_namespace in Smis.DMTF_INTEROP_NAMESPACES:
@@ -707,7 +710,7 @@ class Smis(IStorageAreaNetwork):
cap.set(Capabilities.VOLUME_REPLICATE_CLONE)

if self.RepSvc.RepTypes.SYNC_CLONE_LOCAL in s_rt or \
- self.RepSvc.RepTypes.ASYNC_CLONE_LOCAL in s_rt:
+ self.RepSvc.RepTypes.ASYNC_CLONE_LOCAL in s_rt:
cap.set(Capabilities.VOLUME_REPLICATE_COPY)
else:
# Try older storage configuration service
@@ -728,14 +731,14 @@ class Smis(IStorageAreaNetwork):
if len(sct):
cap.set(Capabilities.VOLUME_REPLICATE)

- # Mirror support is not working and is not supported at
- # this time.
+ # Mirror support is not working and is not supported at
+ # this time.

- # if Smis.CopyTypes.ASYNC in sct:
- # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_ASYNC)
+ # if Smis.CopyTypes.ASYNC in sct:
+ # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_ASYNC)

- # if Smis.CopyTypes.SYNC in sct:
- # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_SYNC)
+ # if Smis.CopyTypes.SYNC in sct:
+ # cap.set(Capabilities.VOLUME_REPLICATE_MIRROR_SYNC)

if Smis.CopyTypes.UNSYNCASSOC in sct:
cap.set(Capabilities.VOLUME_REPLICATE_CLONE)
@@ -1285,7 +1288,7 @@ class Smis(IStorageAreaNetwork):
return self._new_pool(cim_pools[0])

@handle_cim_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
"""
Return all volumes.
We are basing on "Block Services Package" profile version 1.4 or
@@ -1328,7 +1331,7 @@ class Smis(IStorageAreaNetwork):
else:
vol = self._new_vol(cim_vol, pool_id, sys_id)
rc.extend([vol])
- return rc
+ return search_property(rc, search_key, search_value)

def _systems(self, system_name=None):
"""
@@ -1413,7 +1416,7 @@ class Smis(IStorageAreaNetwork):
return pool_pros

@handle_cim_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
"""
We are basing on "Block Services Package" profile version 1.4 or
later:
@@ -1474,7 +1477,7 @@ class Smis(IStorageAreaNetwork):
raise LsmError(ErrorNumber.INTERNAL_ERROR,
"Failed to retrieve pool information " +
"from CIM_StoragePool: %s" % cim_pool.path)
- return rc
+ return search_property(rc, search_key, search_value)

def _sys_id_of_cim_pool(self, cim_pool):
"""
@@ -1526,9 +1529,10 @@ class Smis(IStorageAreaNetwork):
status |= System.STATUS_OK
elif os == Smis.SystemOperationalStatus.DEGRADED:
status |= System.STATUS_DEGRADED
- elif os == Smis.SystemOperationalStatus.ERROR or \
- os == Smis.SystemOperationalStatus.STRESSED or \
- os == Smis.SystemOperationalStatus.NON_RECOVERABLE_ERROR:
+ elif (os == Smis.SystemOperationalStatus.ERROR or
+ os == Smis.SystemOperationalStatus.STRESSED or
+ os ==
+ Smis.SystemOperationalStatus.NON_RECOVERABLE_ERROR):
status |= System.STATUS_ERROR
elif os == Smis.SystemOperationalStatus.PREDICTIVE_FAILURE:
status |= System.STATUS_PREDICTIVE_FAILURE
@@ -2141,10 +2145,12 @@ class Smis(IStorageAreaNetwork):
'Error: access group %s does not exist!' % volume.id)

@handle_cim_errors
- def access_groups(self, flags=0):
+ def access_groups(self, search_key=None, search_value=None, flags=0):
cim_spc_pros = self._new_access_group_cim_spc_pros()
cim_spcs = self._get_access_groups(property_list=cim_spc_pros)
- return [self._new_access_group(cim_spc) for cim_spc in cim_spcs]
+ return search_property(
+ [self._new_access_group(cim_spc) for cim_spc in cim_spcs],
+ search_key, search_value)

def _initiator_lookup(self, initiator_id):
"""
@@ -2194,7 +2200,6 @@ class Smis(IStorageAreaNetwork):
*(self._c.InvokeMethod('HidePaths', ccs.path,
**hide_params)))[0]

-
@handle_cim_errors
def job_free(self, job_id, flags=0):
"""
@@ -2210,7 +2215,7 @@ class Smis(IStorageAreaNetwork):
pass

@handle_cim_errors
- def disks(self, flags=0):
+ def disks(self, search_key=None, search_value=None, flags=0):
"""
return all object of data.Disk.
We are using "Disk Drive Lite Subprofile" v1.4 of SNIA SMI-S for these
@@ -2255,7 +2260,7 @@ class Smis(IStorageAreaNetwork):
cim_ext_pros)

rc.extend([self._new_disk(cim_disk, cim_ext, flags)])
- return rc
+ return search_property(rc, search_key, search_value)

@staticmethod
def _new_disk_cim_disk_pros(flag=0):
@@ -2922,8 +2927,8 @@ class Smis(IStorageAreaNetwork):
cim_sys_pros = self._property_list_of_id("System")
if not self.fallback_mode and \
self._profile_is_supported(SNIA.BLK_SRVS_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False) is None:
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False) is None:
raise LsmError(ErrorNumber.NO_SUPPORT,
"SMI-S %s version %s is not supported" %
(SNIA.BLK_SRVS_PROFILE,
@@ -2953,8 +2958,8 @@ class Smis(IStorageAreaNetwork):
"""
if not self.fallback_mode and \
self._profile_is_supported(SNIA.BLK_SRVS_PROFILE,
- SNIA.SMIS_SPEC_VER_1_4,
- strict=False) is None:
+ SNIA.SMIS_SPEC_VER_1_4,
+ strict=False) is None:
raise LsmError(ErrorNumber.NO_SUPPORT,
"SMI-S %s version %s is not supported" %
(SNIA.BLK_SRVS_PROFILE,
@@ -3085,8 +3090,8 @@ class Smis(IStorageAreaNetwork):
possible_element_names = []
if raid_type == Pool.RAID_TYPE_JBOD:
possible_element_names = ['JBOD']
- elif raid_type == Pool.RAID_TYPE_RAID0 or \
- raid_type == Pool.RAID_TYPE_NOT_APPLICABLE:
+ elif (raid_type == Pool.RAID_TYPE_RAID0 or
+ raid_type == Pool.RAID_TYPE_NOT_APPLICABLE):
possible_element_names = ['RAID0']
elif raid_type == Pool.RAID_TYPE_RAID1:
possible_element_names = ['RAID1']
@@ -3376,7 +3381,7 @@ class Smis(IStorageAreaNetwork):
# we enumerate CIM_ComputerSystem.
try:
cim_scss_path = self._c.EnumerateInstanceNames(
- 'CIM_StorageConfigurationService')
+ 'CIM_StorageConfigurationService')
except CIMError as e:
# If array does not support CIM_StorageConfigurationService
# we use CIM_ComputerSystem which is mandatory.
@@ -3433,7 +3438,6 @@ class Smis(IStorageAreaNetwork):
else:
return cim_syss

-
def _cim_sys_of_id(self, system_id, property_list=None):
"""
Return a CIMInstance of CIM_ComputerSystem for given system id.
diff --git a/plugin/targetd/targetd.py b/plugin/targetd/targetd.py
index 0f82349..b4f4396 100644
--- a/plugin/targetd/targetd.py
+++ b/plugin/targetd/targetd.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python

-# Copyright (C) 2011-2013 Red Hat, Inc.
+# Copyright (C) 2011-2014 Red Hat, Inc.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
@@ -16,14 +16,14 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Author: Andy Grover <agrover at redhat com>
+# Gris Ge <***@redhat.com>

import copy

from lsm import (Pool, Volume, System, Capabilities, Initiator,
IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
LsmError, ErrorNumber, uri_parse, md5, VERSION,
- common_urllib2_error_handler)
-
+ common_urllib2_error_handler, search_property)

import urllib2
import json
@@ -144,7 +144,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

@handle_errors
- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
volumes = []
for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
p['type'] == 'block'):
@@ -154,16 +154,16 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
512, vol['size'] / 512,
Volume.STATUS_OK,
self.system.id, p_name))
- return volumes
+ return search_property(volumes, search_key, search_value)

@handle_errors
- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
pools = []
for pool in self._jsonrequest("pool_list"):
pools.append(Pool(pool['name'], pool['name'], pool['size'],
pool['free_size'], Pool.STATUS_UNKNOWN, '',
'targetd'))
- return pools
+ return search_property(pools, search_key, search_value)

@handle_errors
def initiators(self, flags=0):
@@ -307,14 +307,14 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return inits

@handle_errors
- def fs(self, flags=0):
+ def fs(self, search_key=None, search_value=None, flags=0):
rc = []
for fs in self._jsonrequest("fs_list"):
#self, id, name, total_space, free_space, pool_id, system_id
rc.append(FileSystem(fs['uuid'], fs['name'], fs['total_space'],
fs['free_space'], fs['pool'],
self.system.id))
- return rc
+ return search_property(rc, search_key, search_value)

@handle_errors
def fs_delete(self, fs, flags=0):
@@ -397,7 +397,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
return md5(export_path + opts)

@handle_errors
- def exports(self, flags=0):
+ def exports(self, search_key=None, search_value=None, flags=0):
tmp_exports = {}
exports = []
fs_full_paths = {}
@@ -470,7 +470,7 @@ class TargetdStorage(IStorageAreaNetwork, INfs):
export['path'], sec, root, rw, ro, anonuid,
anongid, TargetdStorage._option_string(options)))

- return exports
+ return search_property(exports, search_key, search_value)

def _get_fs_path(self, fs_id):
for fs in self._jsonrequest("fs_list"):
diff --git a/plugin/v7k/ibmv7k.py b/plugin/v7k/ibmv7k.py
index f7ef393..077503b 100644
--- a/plugin/v7k/ibmv7k.py
+++ b/plugin/v7k/ibmv7k.py
@@ -18,7 +18,8 @@
import paramiko

from lsm import (Capabilities, ErrorNumber, IStorageAreaNetwork, Initiator,
- LsmError, Pool, System, VERSION, Volume, uri_parse)
+ LsmError, Pool, System, VERSION, Volume, uri_parse,
+ search_property)


def handle_ssh_errors(method):
@@ -423,16 +424,20 @@ class IbmV7k(IStorageAreaNetwork):
def plugin_info(self, flags=0):
return "IBM V7000 lsm plugin", VERSION

- def pools(self, flags=0):
+ def pools(self, search_key=None, search_value=None, flags=0):
gp = self._get_pools()
- return [self._pool(p) for p in gp.itervalues()]
+ return search_property(
+ [self._pool(p) for p in gp.itervalues()],
+ search_key, search_value)

def systems(self, flags=0):
return [self.sys_info]

- def volumes(self, flags=0):
+ def volumes(self, search_key=None, search_value=None, flags=0):
gv = self._get_volumes()
- return [self._volume(v) for v in gv.itervalues()]
+ return search_property(
+ [self._volume(v) for v in gv.itervalues()],
+ search_key, search_value)

def initiators(self, flags=0):
init_list = []
--
1.8.3.1
Gris Ge
2014-06-02 10:13:47 UTC
Permalink
* Add 'search_test' to test the search feature of query.

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

diff --git a/test/cmdtest.py b/test/cmdtest.py
index 3042a40..eeeb96e 100755
--- a/test/cmdtest.py
+++ b/test/cmdtest.py
@@ -54,6 +54,7 @@ cmd = "lsmcli"
sep = ","
test_pool_name = 'lsm_test_aggr'
test_fs_pool_id = ''
+test_disk_id = 'DISK_ID_00000'

CUR_SYS_ID = None

@@ -676,6 +677,53 @@ 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
+ if test_fs_pool_id:
+ pool_id = test_fs_pool_id
+ else:
+ pool_id = name_to_id(OP_POOL, test_pool_name)
+ pool_id_filter = "--pool='%s'" % pool_id
+
+ vol_id = create_volume(pool_id)
+ vol_id_filter = "--vol='%s'" % vol_id
+
+ disk_id_filter = "--disk='%s'" % test_disk_id
+
+ ag_id = access_group_create(iqn[0], system_id)
+ ag_id_filter = "--ag='%s'" % ag_id
+
+ fs_id = fs_create(pool_id)
+ fs_id_filter = "--fs='%s'" % fs_id
+
+ nfs_export_id = export_fs(fs_id)
+ nfs_export_id_filter = "--nfs-export='%s'" % nfs_export_id
+
+ all_filters = [sys_id_filter, pool_id_filter, vol_id_filter,
+ disk_id_filter, ag_id_filter, fs_id_filter,
+ nfs_export_id_filter]
+
+ supported = {
+ 'pools': [sys_id_filter, pool_id_filter],
+ 'volumes': [sys_id_filter, pool_id_filter, vol_id_filter],
+ 'disks': [sys_id_filter, disk_id_filter],
+ 'access_groups': [sys_id_filter, ag_id_filter],
+ 'fs': [sys_id_filter, pool_id_filter, fs_id_filter],
+ 'exports': [fs_id_filter, nfs_export_id_filter],
+ }
+ for resouce_type in supported.keys():
+ for cur_filter in all_filters:
+ if cur_filter in supported[resouce_type]:
+ call([cmd, 'list', '--type', resouce_type, cur_filter])
+ else:
+ call([cmd, 'list', '--type', resouce_type, cur_filter], 2)
+
+ un_export_fs(nfs_export_id)
+ delete_fs(fs_id)
+ access_group_delete(ag_id)
+ volume_delete(vol_id)
+ return

def run_all_tests(cap, system_id):
test_display(cap, system_id)
@@ -686,6 +734,8 @@ def run_all_tests(cap, system_id):

test_mapping(cap, system_id)

+ search_test(cap, system_id)
+

if __name__ == "__main__":
parser = OptionParser()
--
1.8.3.1
Tony Asleson
2014-06-02 23:12:12 UTC
Permalink
Looks good!

Patches committed.

Thanks,
Tony
Post by Gris Ge
lsm.Client.pools(self, search_key=None, search_value=None, flags=0)
lsm.Client.volumes(self, search_key=None, search_value=None, flags=0)
lsm.Client.disks(self, search_key=None, search_value=None, flags=0)
lsm.Client.access_groups(self, search_key=None, search_value=None, flags=0)
lsm.Client.fs(self, search_key=None, search_value=None, flags=0)
lsm.Client.exports(self, search_key=None, search_value=None, flags=0)
lsm.Capabilities.POOLS_QUICK_SEARCH
lsm.Capabilities.VOLUMES_QUICK_SEARCH
lsm.Capabilities.DISKS_QUICK_SEARCH
lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
lsm.Capabilities.FS_QUICK_SEARCH
lsm.Capabilities.NFS_EXPORTS_QUICK_SEARCH
lsm.Pool.SUPPORTED_SEARCH_KEYS
lsm.Volume.SUPPORTED_SEARCH_KEYS
lsm.Disk.SUPPORTED_SEARCH_KEYS
lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
lsm.FileSystem.SUPPORTED_SEARCH_KEYS
lsm.NfsExport.SUPPORTED_SEARCH_KEYS
lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
lsm.search_property(search_key, search_value)
# This is for plugin only.
# It could be much better if we let _pluginrunner.py handle the
# general searching in stead of plugin itself. But that will waive out
# the plugin standalone mode. We can discuss this later.
* lsmcli now allowing '--sys' and etc options in list command for searching.
* Tests for lsmcli about query with search.
* Not search support for systems().
* Not using lsm.plug_helper module for general search.
Python library: add search support to query methods
lsmcli: add search support for list command
Plugin: add search support to query methods
Test: Add search test to cmdtest.py
doc/man/lsmcli.1.in | 29 ++++++++
plugin/nstor/nstor.py | 30 ++++-----
plugin/ontap/ontap.py | 44 +++++++-----
plugin/sim/simulator.py | 50 +++++++++-----
plugin/smispy/smis.py | 72 ++++++++++----------
plugin/targetd/targetd.py | 22 +++---
plugin/v7k/ibmv7k.py | 15 +++--
python_binding/lsm/__init__.py | 2 +-
python_binding/lsm/_client.py | 44 +++++++-----
python_binding/lsm/_common.py | 4 ++
python_binding/lsm/_data.py | 21 +++++-
python_binding/lsm/_iplugin.py | 10 +--
python_binding/lsm/_pluginrunner.py | 11 +++
test/cmdtest.py | 50 ++++++++++++++
tools/lsmcli/cmdline.py | 129 +++++++++++++++++++++++++++++++-----
15 files changed, 395 insertions(+), 138 deletions(-)
Loading...