* Using these two hpssacli commands to generate lsm.System:
    hpssacli ctrl all show detail
    hpssacli ctrl all show status

 * New methods in utils.py:
    * cmd_exec()
      # Execute given commands and return STDOUT.

 * New classes in utils.py:
    * ExecError
      # Holding command failure output and return code.

 * New method decorator: @_handle_errors

 * Apply decorator @_handle_errors to all public methods of SmartArray class.

 * Important new internal methods of SmartArray class:
    * _parse_hpssacli_output()
      # Convert output string of hpssacli to python dictionary.
      # The indention of output will be treated as dictionary key/value
      # relationship.
    * _sacli_exec()
      # Execute hpssacli commands and call _parse_hpssacli_output() to
      # parse output string.

Changes in V2:

 * Fix the missing '()' after the error message format in
   _parse_hpssacli_output()

 * Treat hpssacli 'No controllers detected' error as LsmError
   ErrorNumber.NOT_FOUND_SYSTEM.

Changes in V3:

* Moved the 'utils.py' from previous commit to this one along with rpm SPEC
  file and automake files update.

* Fix bug in _parse_hpssacli_output() when 'hpssacli' have this kind of
  message in output:

    Note: Predictive Spare Activation Mode is enabled, physical drives that
    are in predictive failure state will not be available for use as data or
    spare drives.

  We filter out all line start with 'Note:'

Signed-off-by: Gris Ge <f...@redhat.com>
---
 packaging/libstoragemgmt.spec.in |   1 +
 plugin/hpsa/Makefile.am          |   2 +-
 plugin/hpsa/hpsa.py              | 200 ++++++++++++++++++++++++++++++++++++++-
 plugin/hpsa/utils.py             |  48 ++++++++++
 4 files changed, 246 insertions(+), 5 deletions(-)
 create mode 100644 plugin/hpsa/utils.py

diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index 9e24faa..a759c35 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -590,6 +590,7 @@ fi
 %dir %{python_sitelib}/lsm/plugin/hpsa
 %{python_sitelib}/lsm/plugin/hpsa/__init__.*
 %{python_sitelib}/lsm/plugin/hpsa/hpsa.*
+%{python_sitelib}/lsm/plugin/hpsa/utils.*
 %{_bindir}/hpsa_lsmplugin
 %{_sysconfdir}/lsm/pluginconf.d/hpsa.conf
 %{_mandir}/man1/hpsa_lsmplugin.1*
diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
index 46344c0..3076cbc 100644
--- a/plugin/hpsa/Makefile.am
+++ b/plugin/hpsa/Makefile.am
@@ -2,7 +2,7 @@ if WITH_HPSA
 plugindir = $(pythondir)/lsm/plugin
 hpsadir = $(plugindir)/hpsa
 
-hpsa_PYTHON = __init__.py hpsa.py
+hpsa_PYTHON = __init__.py hpsa.py utils.py
 
 dist_bin_SCRIPTS= hpsa_lsmplugin
 endif
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 46a093e..4e4b5ac 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -15,38 +15,230 @@
 #
 # Author: Gris Ge <f...@redhat.com>
 
+import os
+import errno
+
 from lsm import (
-    IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber)
+    IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
+    System)
+
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError
+
+
+def _handle_errors(method):
+    def _wrapper(*args, **kwargs):
+        try:
+            return method(*args, **kwargs)
+        except LsmError:
+            raise
+        except KeyError as key_error:
+            raise LsmError(
+                ErrorNumber.PLUGIN_BUG,
+                "Expected key missing from SmartArray hpssacli output:%s" %
+                key_error)
+        except ExecError as exec_error:
+            if 'No controllers detected' in exec_error.stdout:
+                raise LsmError(
+                    ErrorNumber.NOT_FOUND_SYSTEM,
+                    "No HP SmartArray deteceted by hpssacli.")
+            else:
+                raise LsmError(ErrorNumber.PLUGIN_BUG, str(exec_error))
+        except Exception as common_error:
+            raise LsmError(
+                ErrorNumber.PLUGIN_BUG,
+                "Got unexpected error %s" % common_error)
+
+    return _wrapper
+
+
+def _sys_status_of(hp_ctrl_status):
+    """
+    Base on data of "hpssacli ctrl all show status"
+    """
+    status_info = ''
+    status = System.STATUS_UNKNOWN
+    check_list = [
+        'Controller Status', 'Cache Status', 'Battery/Capacitor Status']
+    for key_name in check_list:
+        if key_name in hp_ctrl_status and hp_ctrl_status[key_name] != 'OK':
+            # TODO(Gris Ge): Beg HP for possible values
+            status = System.STATUS_OTHER
+            status_info += hp_ctrl_status[key_name]
+
+    if status != System.STATUS_OTHER:
+        status = System.STATUS_OK
+
+    return status, status_info
+
+
+def _parse_hpssacli_output(output):
+    """
+    Got a output string of hpssacli to dictionary(nested).
+    """
+    output_lines = [
+        l for l in output.split("\n") if l and not l.startswith('Note:')]
+
+    data = {}
+
+    # Detemine indention level
+    top_indention_level = sorted(
+        set(
+            len(line) - len(line.lstrip())
+            for line in output_lines))[0]
+
+    indent_2_data = {
+        top_indention_level: data
+    }
+
+    for line_num in range(len(output_lines)):
+        cur_line = output_lines[line_num]
+        if cur_line.strip() == 'None attached':
+            continue
+        if line_num + 1 == len(output_lines):
+            nxt_line = ''
+        else:
+            nxt_line = output_lines[line_num + 1]
+
+        cur_indent_count = len(cur_line) - len(cur_line.lstrip())
+        nxt_indent_count = len(nxt_line) - len(nxt_line.lstrip())
+
+        cur_line_splitted = cur_line.split(": ")
+        cur_data_pointer = indent_2_data[cur_indent_count]
+
+        if nxt_indent_count > cur_indent_count:
+            nxt_line_splitted = nxt_line.split(": ")
+            if len(nxt_line_splitted) == 1:
+                new_data = []
+            else:
+                new_data = {}
+
+            if cur_line.lstrip() not in cur_data_pointer:
+                cur_data_pointer[cur_line.lstrip()] = new_data
+                indent_2_data[nxt_indent_count] = new_data
+            elif type(cur_data_pointer[cur_line.lstrip()]) != type(new_data):
+                raise LsmError(
+                    ErrorNumber.PLUGIN_BUG,
+                    "_parse_hpssacli_output(): Unexpected line '%s%s\n'" %
+                    (cur_line, nxt_line))
+        else:
+            if len(cur_line_splitted) == 1:
+                if type(indent_2_data[cur_indent_count]) != list:
+                    raise Exception("not a list: '%s'" % cur_line)
+                cur_data_pointer.append(cur_line.lstrip())
+            else:
+                cur_data_pointer[cur_line_splitted[0].lstrip()] = \
+                    ": ".join(cur_line_splitted[1:]).strip()
+    return data
 
 
 class SmartArray(IPlugin):
+    _DEFAULT_BIN_PATHS = [
+        "/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
+
+    def __init__(self):
+        self._sacli_bin = None
+
+    def _find_sacli(self):
+        """
+        Try _DEFAULT_MDADM_BIN_PATHS
+        """
+        for cur_path in SmartArray._DEFAULT_BIN_PATHS:
+            if os.path.lexists(cur_path):
+                self._sacli_bin = cur_path
+
+        if not self._sacli_bin:
+            raise LsmError(
+                ErrorNumber.INVALID_ARGUMENT,
+                "SmartArray sacli is not installed correctly")
+
+    @_handle_errors
     def plugin_register(self, uri, password, timeout, flags=Client.FLAG_RSVD):
-        pass
+        if os.geteuid() != 0:
+            raise LsmError(
+                ErrorNumber.INVALID_ARGUMENT,
+                "This plugin requires root privilege both daemon and client")
+        uri_parsed = uri_parse(uri)
+        self._sacli_bin = uri_parsed.get('parameters', {}).get('hpssacli')
+        if not self._sacli_bin:
+            self._find_sacli()
 
+        self._sacli_exec(['version'], flag_convert=False)
+
+    @_handle_errors
     def plugin_unregister(self, flags=Client.FLAG_RSVD):
         pass
 
+    @_handle_errors
     def job_status(self, job_id, flags=Client.FLAG_RSVD):
         raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
 
+    @_handle_errors
     def job_free(self, job_id, flags=Client.FLAG_RSVD):
         pass
 
+    @_handle_errors
     def plugin_info(self, flags=Client.FLAG_RSVD):
         return "HP SmartArray Plugin", VERSION
 
+    @_handle_errors
     def time_out_set(self, ms, flags=Client.FLAG_RSVD):
         raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
 
+    @_handle_errors
     def time_out_get(self, flags=Client.FLAG_RSVD):
         raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
 
+    @_handle_errors
     def capabilities(self, system, flags=Client.FLAG_RSVD):
         return Capabilities()
 
-    def systems(self, flags=Client.FLAG_RSVD):
-        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+    def _sacli_exec(self, sacli_cmds, flag_convert=True):
+        """
+        If flag_convert is True, convert data into dict.
+        """
+        sacli_cmds.insert(0, self._sacli_bin)
+        try:
+            output = cmd_exec(sacli_cmds)
+        except OSError as os_error:
+            if os_error.errno == errno.ENOENT:
+                raise LsmError(
+                    ErrorNumber.INVALID_ARGUMENT,
+                    "hpssacli binary '%s' is not exist or executable." %
+                    self._sacli_bin)
+            else:
+                raise
+
+        if flag_convert:
+            return _parse_hpssacli_output(output)
+        else:
+            return output
+
+    @_handle_errors
+    def systems(self, flags=0):
+        """
+        Depend on command:
+            hpssacli ctrl all show detail
+            hpssacli ctrl all show status
+        """
+        rc_lsm_syss = []
+        ctrl_all_show = self._sacli_exec(
+            ["ctrl", "all", "show", "detail"])
+        ctrl_all_status = self._sacli_exec(
+            ["ctrl", "all", "show", "status"])
+
+        for ctrl_name in ctrl_all_show.keys():
+            ctrl_data = ctrl_all_show[ctrl_name]
+            sys_id = ctrl_data['Serial Number']
+            (status, status_info) = _sys_status_of(ctrl_all_status[ctrl_name])
+
+            plugin_data = "%s" % ctrl_data['Slot']
+
+            rc_lsm_syss.append(
+                System(sys_id, ctrl_name, status, status_info, plugin_data))
+
+        return rc_lsm_syss
 
+    @_handle_errors
     def pools(self, search_key=None, search_value=None,
               flags=Client.FLAG_RSVD):
         raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
new file mode 100644
index 0000000..67734b0
--- /dev/null
+++ b/plugin/hpsa/utils.py
@@ -0,0 +1,48 @@
+## Copyright (C) 2015 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 <f...@redhat.com>
+
+import subprocess
+import os
+
+
+def cmd_exec(cmds):
+    """
+    Execute provided command and return the STDOUT as string.
+    Raise ExecError if command return code is not zero
+    """
+    cmd_popen = subprocess.Popen(
+        cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+        env={"PATH": os.getenv("PATH")})
+    str_stdout = "".join(list(cmd_popen.stdout)).strip()
+    str_stderr = "".join(list(cmd_popen.stderr)).strip()
+    errno = cmd_popen.wait()
+    if errno != 0:
+        raise ExecError(" ".join(cmds), errno, str_stdout, str_stderr)
+    return str_stdout
+
+
+class ExecError(Exception):
+    def __init__(self, cmd, errno, stdout, stderr, *args, **kwargs):
+        Exception.__init__(self, *args, **kwargs)
+        self.cmd = cmd
+        self.errno = errno
+        self.stdout = stdout
+        self.stderr = stderr
+
+    def __str__(self):
+        return "cmd: '%s', errno: %d, stdout: '%s', stderr: '%s'" % \
+            (self.cmd, self.errno, self.stdout, self.stderr)
-- 
1.8.3.1


------------------------------------------------------------------------------
Dive into the World of Parallel Programming The Go Parallel Website, sponsored
by Intel and developed in partnership with Slashdot Media, is your hub for all
things parallel software development, from weekly thought leadership blogs to
news, videos, case studies, tutorials and more. Take a look and join the 
conversation now. http://goparallel.sourceforge.net/
_______________________________________________
Libstoragemgmt-devel mailing list
Libstoragemgmt-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/libstoragemgmt-devel

Reply via email to