Discussion:
[Libstoragemgmt-devel] [PATCH] Demo: Add HP SmartArray plugin.
Gris Ge
2015-03-12 16:37:34 UTC
Permalink
* Do not commit. This is just a demo.

* With support of:
* systems()
* pools()
* volumes()
* disks() with Disk.STATUS_FREE
* volume_raid_info()

* A lot of TODOs in code. I am seeking a hpsa server with a lot of disks.[1]

[1] If anyone have a server with faulty disk or spare disk,
please email me the output of 'hpssacli show config detail'.

Patches is based on:
[PATCH V2 0/7] Add new disk status: STATUS_FREE

Signed-off-by: Gris Ge <***@redhat.com>
---
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 11 +
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 523 ++++++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 55 +++++
7 files changed, 636 insertions(+)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py

diff --git a/config/pluginconf.d/hpsa.conf b/config/pluginconf.d/hpsa.conf
new file mode 100644
index 0000000..35b52d4
--- /dev/null
+++ b/config/pluginconf.d/hpsa.conf
@@ -0,0 +1 @@
+require-root-privilege = true;
diff --git a/configure.ac b/configure.ac
index 74e6811..56a4fba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -190,6 +190,16 @@ AC_ARG_WITH([megaraid],
[],
[with_megaraid=yes])

+dnl ==========================================================================
+dnl Add option '--without-hpsa' to exclude hpsa plugin.
+dnl ==========================================================================
+
+AC_ARG_WITH([hpsa],
+ [AS_HELP_STRING([--without-hpsa],
+ [Do not build the HP SmartArray plugin])],
+ [],
+ [with_hpsa=yes])
+
AM_CONDITIONAL([WITH_MEGARAID], [test "x$with_megaraid" = "xyes"])

dnl ==========================================================================
@@ -221,6 +231,7 @@ AC_OUTPUT(libstoragemgmt.pc \
plugin/Makefile \
plugin/simc/Makefile \
plugin/megaraid/Makefile \
+ plugin/hpsa/Makefile \
daemon/Makefile \
config/Makefile \
doc/Makefile \
diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
new file mode 100644
index 0000000..3076cbc
--- /dev/null
+++ b/plugin/hpsa/Makefile.am
@@ -0,0 +1,8 @@
+if WITH_HPSA
+plugindir = $(pythondir)/lsm/plugin
+hpsadir = $(plugindir)/hpsa
+
+hpsa_PYTHON = __init__.py hpsa.py utils.py
+
+dist_bin_SCRIPTS= hpsa_lsmplugin
+endif
diff --git a/plugin/hpsa/__init__.py b/plugin/hpsa/__init__.py
new file mode 100644
index 0000000..61332b4
--- /dev/null
+++ b/plugin/hpsa/__init__.py
@@ -0,0 +1 @@
+from lsm.plugin.hpsa.hpsa import SmartArray
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
new file mode 100644
index 0000000..cfb176c
--- /dev/null
+++ b/plugin/hpsa/hpsa.py
@@ -0,0 +1,523 @@
+# 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 <***@redhat.com>
+
+import os
+from string import strip
+import re
+import errno
+
+from lsm import (uri_parse, search_property, size_human_2_size_bytes,
+ Capabilities, LsmError, ErrorNumber, System, Client,
+ Disk, VERSION, search_property, IPlugin, Pool, Volume)
+
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read
+
+# Naming scheme
+
+
+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:
+ 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 _disk_type_of(hp_disk):
+ disk_interface = hp_disk['Interface Type']
+ if disk_interface == 'SATA':
+ return Disk.TYPE_SATA
+ elif disk_interface == 'Solid State SATA':
+ return Disk.TYPE_SSD
+ elif disk_interface == 'SAS':
+ return Disk.TYPE_SAS
+
+ return Disk.TYPE_UNKNOWN
+
+
+def _disk_status_of(hp_disk, flag_free):
+ # TODO(Gris Ge): Need more document or test for non-OK disks.
+ if hp_disk['Status'] == 'OK':
+ disk_status = Disk.STATUS_OK
+ else:
+ disk_status = Disk.STATUS_OTHER
+
+ if flag_free:
+ disk_status |= Disk.STATUS_FREE
+
+ return disk_status
+
+
+def _hp_size_to_lsm(hp_size):
+ """
+ HP Using 'TB, GB, MB, KB' and etc, for LSM, they are 'TiB' and etc.
+ Return int of block bytes
+ """
+ re_regex = re.compile("^([0-9\.]+) +([EPTGMK])B$")
+ re_match = re_regex.match(hp_size)
+ if re_match:
+ return size_human_2_size_bytes(
+ "%s%siB" % (re_match.group(1), re_match.group(2)))
+
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_hp_size_to_lsm(): Got unexpected HP size string %s" %
+ hp_size)
+
+
+def _pool_status_of(hp_array):
+ """
+ Return (status, status_info)
+ """
+ if hp_array['Status'] == 'OK':
+ return Pool.STATUS_OK, ''
+ else:
+ # TODO(Gris Ge): Try degrade a RAID or fail a RAID.
+ return Pool.STATUS_OTHER, hp_array['Status']
+
+
+def _pool_id_of(sys_id, array_name):
+ return "%s:%s" % (sys_id, array_name.replace(' ', ''))
+
+
+def _hp_raid_type_to_lsm(hp_ld):
+ """
+ Based on this property:
+ Fault Tolerance: 0/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ if hp_raid_level == '0':
+ return Volume.RAID_TYPE_RAID0
+ elif hp_raid_level == '5':
+ return Volume.RAID_TYPE_RAID5
+ elif hp_raid_level == '6':
+ return Volume.RAID_TYPE_RAID6
+ elif hp_raid_level == '1+0':
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
+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 ]
+
+ 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):
+ 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('sacli')
+ 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 "LSI 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):
+ cur_lsm_syss = self.systems()
+ if system.id not in list(s.id for s in cur_lsm_syss):
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_SYSTEM,
+ "System not found")
+ cap = Capabilities()
+ cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUMES)
+ return cap
+
+ 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
+
+ @staticmethod
+ def _lsm_status_of_ctrl(ctrl_status):
+ status_info = ''
+ status = System.STATUS_UNKNOWN
+ check_list = [
+ 'Controller Status', 'Cache Status', 'Battery/Capacitor Status']
+ for key_name in check_list:
+ if ctrl_status[key_name] != 'OK':
+ # TODO(Gris Ge): Beg HP for possible values
+ status = System.STATUS_OTHER
+ status_info += ctrl_status[key_name]
+ if status != System.STATUS_OTHER:
+ status = System.STATUS_OK
+
+ return status, status_info
+
+ def _sys_id_of_ctrl_num(self, ctrl_num, ctrl_show_all_output=None):
+ if ctrl_show_all_output is None:
+ return self._sacli_exec(
+ ["/c%d" % ctrl_num, "show"])['Serial Number']
+ else:
+ return ctrl_show_all_output['Basics']['Serial Number']
+
+ @_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"])
+ print ctrl_all_show.items()
+ print ctrl_all_status.items()
+
+ for ctrl_name in ctrl_all_show.keys():
+ ctrl_data = ctrl_all_show[ctrl_name]
+ sys_id = ctrl_data['Serial Number']
+ (status, status_info) = SmartArray._lsm_status_of_ctrl(
+ 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
+
+ @staticmethod
+ def _hp_disk_to_lsm_disk(hp_disk, sys_id, ctrl_num, key_name,
+ flag_free=False):
+ disk_id = hp_disk['Serial Number']
+ disk_num = key_name[len("physicaldrive "):]
+ disk_name = "%s %s" % (hp_disk['Model'], disk_num)
+ disk_type = _disk_type_of(hp_disk)
+ blk_size = int(hp_disk['Native Block Size'])
+ blk_count = int(_hp_size_to_lsm(hp_disk['Size']) / blk_size)
+ status = _disk_status_of(hp_disk, flag_free)
+ plugin_data = "%s:%s" % (ctrl_num, disk_num)
+
+ return Disk(
+ disk_id, disk_name, disk_type, blk_size, blk_count,
+ status, sys_id, plugin_data)
+
+ @_handle_errors
+ def disks(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ # TODO(Gris Ge): Need real test on spare disk and free disk.
+ # The free disks is purely base on HP document.
+ rc_lsm_disks = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ if key_name == 'unassigned':
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ return search_property(rc_lsm_disks, search_key, search_value)
+
+ def _hp_array_to_lsm_pool(self, hp_array, array_name, sys_id, ctrl_num):
+ print hp_array.items()
+ pool_id = _pool_id_of(sys_id, array_name)
+ name = array_name
+ elem_type = Pool.ELEMENT_TYPE_VOLUME | Pool.ELEMENT_TYPE_VOLUME_FULL
+ unsupported_actions = 0
+ # TODO(Gris Ge): HP does not provide a precise number of bytes.
+ free_space = _hp_size_to_lsm(hp_array['Unused Space'])
+ total_space = free_space
+ for key_name in hp_array.keys():
+ if key_name.startswith('Logical Drive'):
+ total_space += _hp_size_to_lsm(hp_array[key_name]['Size'])
+
+ (status, status_info) = _pool_status_of(hp_array)
+
+ plugin_data = "%s:%s" % (
+ ctrl_num, array_name[len("Array: "):])
+
+ return Pool(
+ pool_id, name, elem_type, unsupported_actions,
+ total_space, free_space, status, status_info,
+ sys_id, plugin_data)
+
+ @_handle_errors
+ def pools(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_pools = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ lsm_pools.append(
+ self._hp_array_to_lsm_pool(
+ ctrl_data[key_name], key_name, sys_id, ctrl_num))
+
+ return search_property(lsm_pools, search_key, search_value)
+
+ @staticmethod
+ def _hp_ld_to_lsm_vol(hp_ld, pool_id, sys_id, ctrl_num, array_num,
+ hp_ld_name):
+ ld_num = hp_ld_name[len("Logical Drive: "):]
+ name = hp_ld_name
+ vpd83 = hp_ld['Unique Identifier'].lower()
+ vol_id = vpd83
+ # No document or command output indicate block size
+ # of volume. So we try to read from linux kernel, if failed
+ # try 512 and roughly calculate the sector count.
+ sd_name_regex = re.compile("/dev/(sd[a-z]+)")
+ regex_match = sd_name_regex.search(hp_ld['Disk Name'])
+ if regex_match:
+ sd_name = regex_match.group(1)
+ block_size = int(file_read(
+ "/sys/block/%s/queue/logical_block_size" % sd_name))
+ num_of_blocks = int(file_read("/sys/block/%s/size" % sd_name))
+ else:
+ block_size = 512
+ num_of_blocks = int(_hp_size_to_lsm(hp_ld['Size']) / block_size)
+
+ # HP SmartArray does not allow disabling volume.
+ admin_state = Volume.ADMIN_STATE_ENABLED
+
+ plugin_data = "%s:%s:%s" % (ctrl_num, array_num, ld_num)
+
+ return Volume(
+ vol_id, name, vpd83, block_size, num_of_blocks, admin_state,
+ sys_id, pool_id, plugin_data)
+
+ @_handle_errors
+ def volumes(self, search_key=None, search_value=None, flags=0):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_vols = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ ctrl_num = ctrl_data['Slot']
+ sys_id = ctrl_data['Serial Number']
+ for key_name in ctrl_data.keys():
+ if not key_name.startswith("Array:"):
+ continue
+ pool_id = _pool_id_of(sys_id, key_name)
+ array_num = key_name[len('Array: '):]
+ for array_key_name in ctrl_data[key_name].keys():
+ if not array_key_name.startswith("Logical Drive"):
+ continue
+ lsm_vols.append(
+ SmartArray._hp_ld_to_lsm_vol(
+ ctrl_data[key_name][array_key_name],
+ pool_id, sys_id, ctrl_num, array_num,
+ array_key_name))
+
+ return search_property(lsm_vols, search_key, search_value)
+
+ @_handle_errors
+ def volume_raid_info(self, volume, flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl slot=0 show config detail
+ """
+ if not volume.plugin_data:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+
+ ctrl_conf = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"])
+ ctrl_data = ctrl_conf.values()[0]
+ disk_count = 0
+ flag_found = False
+ for key_name in ctrl_data.keys():
+ if key_name != "Array: %s" % array_num:
+ continue
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name == "Logical Drive: %s" % ld_num:
+ hp_ld = ctrl_data[key_name][array_key_name]
+ flag_found = True
+ raid_type = _hp_raid_type_to_lsm(
+ hp_ld)
+ strip_size = _hp_size_to_lsm(
+ hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(
+ hp_ld['Full Stripe Size'])
+ elif array_key_name.startswith("physicaldrive"):
+ hp_disk = ctrl_data[key_name][array_key_name]
+ if hp_disk['Drive Type'] == 'Data Drive':
+ disk_count += 1
+
+ if flag_found is False:
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
diff --git a/plugin/hpsa/hpsa_lsmplugin b/plugin/hpsa/hpsa_lsmplugin
new file mode 100755
index 0000000..07230f5
--- /dev/null
+++ b/plugin/hpsa/hpsa_lsmplugin
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+# 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: tasleson
+# Gris Ge <***@redhat.com>
+
+import sys
+import syslog
+import traceback
+
+try:
+ from lsm import PluginRunner
+ from lsm.plugin.hpsa import SmartArray
+
+ if __name__ == '__main__':
+ PluginRunner(SmartArray, sys.argv).run()
+except Exception:
+ #This should be quite rare, but when it does happen this is pretty
+ #key in understanding what happened, especially when it happens when
+ #running from the daemon.
+ msg = str(traceback.format_exc())
+ syslog.syslog(syslog.LOG_ERR, msg)
+ sys.stderr.write(msg)
+ sys.exit(1)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
new file mode 100644
index 0000000..23ae6cd
--- /dev/null
+++ b/plugin/hpsa/utils.py
@@ -0,0 +1,55 @@
+## 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 <***@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
+
+
+def file_read(file_path):
+ """
+ Read file and return string of file content.
+ Use 'cat' command to better utilize the ExecError.
+ """
+ return cmd_exec(['cat', file_path])
+
+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
Tony Asleson
2015-03-12 18:40:49 UTC
Permalink
Hi Gris,

Nice work!

Notes: so far from taking a clean git repo and applying this patch and
running on a box I got access too (not your box):

* autogen.sh fails, plugin/hpsa/Makefile.am:1: error: WITH_HPSA does not
appear in AM_CONDITIONAL. I commented that part out to get by, need add
to configure.ac

* The debug prints make for lots of output :-)

* My config must be slightly different from yours?

# ./hpsa_lsmplugin --uri hpsa:// list --type systems
[('Smart Array P410i in Slot 0 (Embedded)', {'Slot': '0', 'Driver Name':
'hpsa', 'SATA NCQ Supported': 'True', 'RAID 6 (ADG) Status': 'Disabled',
'Expand Priority': 'Medium', 'Surface Scan Delay': '15 secs', 'Drive
Write Cache': 'Disabled', 'Cache Board Present': 'False', 'Post Prompt
Timeout': '0 secs', 'Total Cache Size': '0 MB', 'Surface Scan Mode':
'Idle', 'Hardware Revision': 'C', 'Serial Number': '50014380100A1560',
'Bus Interface': 'PCI', 'Controller Status': 'OK', 'Driver Supports HP
SSD Smart Path': 'False', 'Encryption Supported': 'False', 'Number of
Ports': '2 Internal only', 'Rebuild Priority': 'Medium', 'Firmware
Version': '5.02', 'Surface Analysis Inconsistency Notification':
'Disabled', 'Wait for Cache Room': 'Disabled', 'Driver Version': '3.4.2'})]
[('Smart Array P410i in Slot 0 (Embedded)', {'Controller Status': 'OK'})]
PLUGIN_BUG(2): Expected key missing from SmartArray hpssacli
output:'Cache Status'

My card does not have 'Cache Status' and 'Battery/Capacitor Status'

* volumes, volume-raid-info, pools worked
* Capabilities failed just like list --type systems, but I see in the
code that the capability for volume raid info is missing

* I'm getting an 'UNKNOWN' raid type for my config,
=> 'Fault Tolerance': '1'

* What about doing this to accommodate card differences?

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index cfb176c..8c0297e 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -281,10 +281,11 @@ class SmartArray(IPlugin):
check_list = [
'Controller Status', 'Cache Status', 'Battery/Capacitor
Status']
for key_name in check_list:
- if ctrl_status[key_name] != 'OK':
- # TODO(Gris Ge): Beg HP for possible values
- status = System.STATUS_OTHER
- status_info += ctrl_status[key_name]
+ if key_name in ctrl_status:
+ if ctrl_status[key_name] != 'OK':
+ # TODO(Gris Ge): Beg HP for possible values
+ status = System.STATUS_OTHER
+ status_info += ctrl_status[key_name]
if status != System.STATUS_OTHER:
status = System.STATUS_OK


Thanks!
-Tony
Post by Gris Ge
* Do not commit. This is just a demo.
* systems()
* pools()
* volumes()
* disks() with Disk.STATUS_FREE
* volume_raid_info()
* A lot of TODOs in code. I am seeking a hpsa server with a lot of disks.[1]
[1] If anyone have a server with faulty disk or spare disk,
please email me the output of 'hpssacli show config detail'.
[PATCH V2 0/7] Add new disk status: STATUS_FREE
---
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 11 +
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 523 ++++++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 55 +++++
7 files changed, 636 insertions(+)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
diff --git a/config/pluginconf.d/hpsa.conf b/config/pluginconf.d/hpsa.conf
new file mode 100644
index 0000000..35b52d4
--- /dev/null
+++ b/config/pluginconf.d/hpsa.conf
@@ -0,0 +1 @@
+require-root-privilege = true;
diff --git a/configure.ac b/configure.ac
index 74e6811..56a4fba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -190,6 +190,16 @@ AC_ARG_WITH([megaraid],
[],
[with_megaraid=yes])
+dnl ==========================================================================
+dnl Add option '--without-hpsa' to exclude hpsa plugin.
+dnl ==========================================================================
+
+AC_ARG_WITH([hpsa],
+ [AS_HELP_STRING([--without-hpsa],
+ [Do not build the HP SmartArray plugin])],
+ [],
+ [with_hpsa=yes])
+
AM_CONDITIONAL([WITH_MEGARAID], [test "x$with_megaraid" = "xyes"])
dnl ==========================================================================
@@ -221,6 +231,7 @@ AC_OUTPUT(libstoragemgmt.pc \
plugin/Makefile \
plugin/simc/Makefile \
plugin/megaraid/Makefile \
+ plugin/hpsa/Makefile \
daemon/Makefile \
config/Makefile \
doc/Makefile \
diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
new file mode 100644
index 0000000..3076cbc
--- /dev/null
+++ b/plugin/hpsa/Makefile.am
@@ -0,0 +1,8 @@
+if WITH_HPSA
+plugindir = $(pythondir)/lsm/plugin
+hpsadir = $(plugindir)/hpsa
+
+hpsa_PYTHON = __init__.py hpsa.py utils.py
+
+dist_bin_SCRIPTS= hpsa_lsmplugin
+endif
diff --git a/plugin/hpsa/__init__.py b/plugin/hpsa/__init__.py
new file mode 100644
index 0000000..61332b4
--- /dev/null
+++ b/plugin/hpsa/__init__.py
@@ -0,0 +1 @@
+from lsm.plugin.hpsa.hpsa import SmartArray
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
new file mode 100644
index 0000000..cfb176c
--- /dev/null
+++ b/plugin/hpsa/hpsa.py
@@ -0,0 +1,523 @@
+# 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
+#
+
+import os
+from string import strip
+import re
+import errno
+
+from lsm import (uri_parse, search_property, size_human_2_size_bytes,
+ Capabilities, LsmError, ErrorNumber, System, Client,
+ Disk, VERSION, search_property, IPlugin, Pool, Volume)
+
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read
+
+# Naming scheme
+
+
+ return method(*args, **kwargs)
+ raise
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "Expected key missing from SmartArray hpssacli output:%s" %
+ key_error)
+ raise LsmError(ErrorNumber.PLUGIN_BUG, str(exec_error))
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "Got unexpected error %s" % common_error)
+
+ return _wrapper
+
+
+ disk_interface = hp_disk['Interface Type']
+ return Disk.TYPE_SATA
+ return Disk.TYPE_SSD
+ return Disk.TYPE_SAS
+
+ return Disk.TYPE_UNKNOWN
+
+
+ # TODO(Gris Ge): Need more document or test for non-OK disks.
+ disk_status = Disk.STATUS_OK
+ disk_status = Disk.STATUS_OTHER
+
+ disk_status |= Disk.STATUS_FREE
+
+ return disk_status
+
+
+ """
+ HP Using 'TB, GB, MB, KB' and etc, for LSM, they are 'TiB' and etc.
+ Return int of block bytes
+ """
+ re_regex = re.compile("^([0-9\.]+) +([EPTGMK])B$")
+ re_match = re_regex.match(hp_size)
+ return size_human_2_size_bytes(
+ "%s%siB" % (re_match.group(1), re_match.group(2)))
+
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_hp_size_to_lsm(): Got unexpected HP size string %s" %
+ hp_size)
+
+
+ """
+ Return (status, status_info)
+ """
+ return Pool.STATUS_OK, ''
+ # TODO(Gris Ge): Try degrade a RAID or fail a RAID.
+ return Pool.STATUS_OTHER, hp_array['Status']
+
+
+ return "%s:%s" % (sys_id, array_name.replace(' ', ''))
+
+
+ """
+ Fault Tolerance: 0/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ return Volume.RAID_TYPE_RAID0
+ return Volume.RAID_TYPE_RAID5
+ return Volume.RAID_TYPE_RAID6
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
+ """
+ Got a output string of hpssacli to dictionary(nested).
+ """
+ output_lines = [ l for l in output.split("\n") if l ]
+
+ 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
+ }
+
+ cur_line = output_lines[line_num]
+ continue
+ nxt_line = ''
+ 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]
+
+ nxt_line_splitted = nxt_line.split(": ")
+ new_data = []
+ new_data = {}
+
+ cur_data_pointer[cur_line.lstrip()] = new_data
+ indent_2_data[nxt_indent_count] = new_data
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_parse_hpssacli_output(): Unexpected line '%s%s\n'" %
+ cur_line, nxt_line)
+ raise Exception("not a list: '%s'" % cur_line)
+ cur_data_pointer.append(cur_line.lstrip())
+ cur_data_pointer[cur_line_splitted[0].lstrip()] = \
+ ": ".join(cur_line_splitted[1:]).strip()
+ return data
+
+
+ _DEFAULT_BIN_PATHS = [
+ "/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
+
+ self._sacli_bin = None
+
+ """
+ Try _DEFAULT_MDADM_BIN_PATHS
+ """
+ self._sacli_bin = cur_path
+
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "SmartArray sacli is not installed correctly")
+
+ 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('sacli')
+ self._find_sacli()
+
+ self._sacli_exec(['version'], flag_convert=False)
+
+ pass
+
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ pass
+
+ return "LSI SmartArray Plugin", VERSION
+
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ cur_lsm_syss = self.systems()
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_SYSTEM,
+ "System not found")
+ cap = Capabilities()
+ cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUMES)
+ return cap
+
+ """
+ If flag_convert is True, convert data into dict.
+ """
+ sacli_cmds.insert(0, self._sacli_bin)
+ output = cmd_exec(sacli_cmds)
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "hpssacli binary '%s' is not exist or executable." %
+ self._sacli_bin)
+ raise
+
+ return _parse_hpssacli_output(output)
+ return output
+
+ status_info = ''
+ status = System.STATUS_UNKNOWN
+ check_list = [
+ 'Controller Status', 'Cache Status', 'Battery/Capacitor Status']
+ # TODO(Gris Ge): Beg HP for possible values
+ status = System.STATUS_OTHER
+ status_info += ctrl_status[key_name]
+ status = System.STATUS_OK
+
+ return status, status_info
+
+ return self._sacli_exec(
+ ["/c%d" % ctrl_num, "show"])['Serial Number']
+ return ctrl_show_all_output['Basics']['Serial Number']
+
+ """
+ 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"])
+ print ctrl_all_show.items()
+ print ctrl_all_status.items()
+
+ ctrl_data = ctrl_all_show[ctrl_name]
+ sys_id = ctrl_data['Serial Number']
+ (status, status_info) = SmartArray._lsm_status_of_ctrl(
+ 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
+
+ def _hp_disk_to_lsm_disk(hp_disk, sys_id, ctrl_num, key_name,
+ disk_id = hp_disk['Serial Number']
+ disk_num = key_name[len("physicaldrive "):]
+ disk_name = "%s %s" % (hp_disk['Model'], disk_num)
+ disk_type = _disk_type_of(hp_disk)
+ blk_size = int(hp_disk['Native Block Size'])
+ blk_count = int(_hp_size_to_lsm(hp_disk['Size']) / blk_size)
+ status = _disk_status_of(hp_disk, flag_free)
+ plugin_data = "%s:%s" % (ctrl_num, disk_num)
+
+ return Disk(
+ disk_id, disk_name, disk_type, blk_size, blk_count,
+ status, sys_id, plugin_data)
+
+ def disks(self, search_key=None, search_value=None,
+ """
+ hpssacli ctrl all show config detail
+ """
+ # TODO(Gris Ge): Need real test on spare disk and free disk.
+ # The free disks is purely base on HP document.
+ rc_lsm_disks = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ return search_property(rc_lsm_disks, search_key, search_value)
+
+ print hp_array.items()
+ pool_id = _pool_id_of(sys_id, array_name)
+ name = array_name
+ elem_type = Pool.ELEMENT_TYPE_VOLUME | Pool.ELEMENT_TYPE_VOLUME_FULL
+ unsupported_actions = 0
+ # TODO(Gris Ge): HP does not provide a precise number of bytes.
+ free_space = _hp_size_to_lsm(hp_array['Unused Space'])
+ total_space = free_space
+ total_space += _hp_size_to_lsm(hp_array[key_name]['Size'])
+
+ (status, status_info) = _pool_status_of(hp_array)
+
+ plugin_data = "%s:%s" % (
+ ctrl_num, array_name[len("Array: "):])
+
+ return Pool(
+ pool_id, name, elem_type, unsupported_actions,
+ total_space, free_space, status, status_info,
+ sys_id, plugin_data)
+
+ def pools(self, search_key=None, search_value=None,
+ """
+ hpssacli ctrl all show config detail
+ """
+ lsm_pools = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ lsm_pools.append(
+ self._hp_array_to_lsm_pool(
+ ctrl_data[key_name], key_name, sys_id, ctrl_num))
+
+ return search_property(lsm_pools, search_key, search_value)
+
+ def _hp_ld_to_lsm_vol(hp_ld, pool_id, sys_id, ctrl_num, array_num,
+ ld_num = hp_ld_name[len("Logical Drive: "):]
+ name = hp_ld_name
+ vpd83 = hp_ld['Unique Identifier'].lower()
+ vol_id = vpd83
+ # No document or command output indicate block size
+ # of volume. So we try to read from linux kernel, if failed
+ # try 512 and roughly calculate the sector count.
+ sd_name_regex = re.compile("/dev/(sd[a-z]+)")
+ regex_match = sd_name_regex.search(hp_ld['Disk Name'])
+ sd_name = regex_match.group(1)
+ block_size = int(file_read(
+ "/sys/block/%s/queue/logical_block_size" % sd_name))
+ num_of_blocks = int(file_read("/sys/block/%s/size" % sd_name))
+ block_size = 512
+ num_of_blocks = int(_hp_size_to_lsm(hp_ld['Size']) / block_size)
+
+ # HP SmartArray does not allow disabling volume.
+ admin_state = Volume.ADMIN_STATE_ENABLED
+
+ plugin_data = "%s:%s:%s" % (ctrl_num, array_num, ld_num)
+
+ return Volume(
+ vol_id, name, vpd83, block_size, num_of_blocks, admin_state,
+ sys_id, pool_id, plugin_data)
+
+ """
+ hpssacli ctrl all show config detail
+ """
+ lsm_vols = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ ctrl_num = ctrl_data['Slot']
+ sys_id = ctrl_data['Serial Number']
+ continue
+ pool_id = _pool_id_of(sys_id, key_name)
+ array_num = key_name[len('Array: '):]
+ continue
+ lsm_vols.append(
+ SmartArray._hp_ld_to_lsm_vol(
+ ctrl_data[key_name][array_key_name],
+ pool_id, sys_id, ctrl_num, array_num,
+ array_key_name))
+
+ return search_property(lsm_vols, search_key, search_value)
+
+ """
+ hpssacli ctrl slot=0 show config detail
+ """
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+
+ ctrl_conf = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"])
+ ctrl_data = ctrl_conf.values()[0]
+ disk_count = 0
+ flag_found = False
+ continue
+ hp_ld = ctrl_data[key_name][array_key_name]
+ flag_found = True
+ raid_type = _hp_raid_type_to_lsm(
+ hp_ld)
+ strip_size = _hp_size_to_lsm(
+ hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(
+ hp_ld['Full Stripe Size'])
+ hp_disk = ctrl_data[key_name][array_key_name]
+ disk_count += 1
+
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
diff --git a/plugin/hpsa/hpsa_lsmplugin b/plugin/hpsa/hpsa_lsmplugin
new file mode 100755
index 0000000..07230f5
--- /dev/null
+++ b/plugin/hpsa/hpsa_lsmplugin
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+# 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: tasleson
+
+import sys
+import syslog
+import traceback
+
+ from lsm import PluginRunner
+ from lsm.plugin.hpsa import SmartArray
+
+ PluginRunner(SmartArray, sys.argv).run()
+ #This should be quite rare, but when it does happen this is pretty
+ #key in understanding what happened, especially when it happens when
+ #running from the daemon.
+ msg = str(traceback.format_exc())
+ syslog.syslog(syslog.LOG_ERR, msg)
+ sys.stderr.write(msg)
+ sys.exit(1)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
new file mode 100644
index 0000000..23ae6cd
--- /dev/null
+++ b/plugin/hpsa/utils.py
@@ -0,0 +1,55 @@
+## 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
+#
+
+import subprocess
+import os
+
+
+ """
+ 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()
+ raise ExecError(" ".join(cmds), errno, str_stdout, str_stderr)
+ return str_stdout
+
+
+ """
+ Read file and return string of file content.
+ Use 'cat' command to better utilize the ExecError.
+ """
+ return cmd_exec(['cat', file_path])
+
+ Exception.__init__(self, *args, **kwargs)
+ self.cmd = cmd
+ self.errno = errno
+ self.stdout = stdout
+ self.stderr = stderr
+
+ return "cmd: '%s', errno: %d, stdout: '%s', stderr: '%s'" % \
+ (self.cmd, self.errno, self.stdout, self.stderr)
Gris Ge
2015-03-13 02:49:56 UTC
Permalink
Post by Tony Asleson
Hi Gris,
Nice work!
Notes: so far from taking a clean git repo and applying this patch and
* What about doing this to accommodate card differences?
Hi Tony,

Thanks for all the comments. I will fix them in coming patch set.
I will code a autotest(for internal lab only) to run this on different
hpsa servers.

Best regards.
--
Gris Ge
Gris Ge
2015-03-14 13:33:01 UTC
Permalink
* This patch set is based on:
[PATCH V3 0/9] Add new disk status: STATUS_FREE

* The new hpsa plugin is based on HP binary tool -- hpssacli which
could be downloaded from:
http://downloads.linux.hp.com/SDR/project/spp/

* This patch set introduced these methods support to hpsa plugin:
* lsm.Client.systems()
* lsm.Client.pools()
* lsm.Client.volumes()
* lsm.Client.disks()
# With Disk.STATUS_FREE support also.
* lsm.Client.volume_raid_info()

* This patch set only coded and tested on 2 servers(Tony's and mine), a
automatic tests is running in our internal lab to test this plugin on more
HP SmartArray cards, will provide subsequent patch if found any bug.

Gris Ge (7):
New plugin: HP SmartArray Plugin.
HP SmartArray Plugin: Add systems() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.disks() support.
HP SmartArray Plugin: Add lsm.Client.volume_raid_info() support.
Git config: Nicer diff output for python scripts.

.gitattributes | 1 +
config/Makefile.am | 5 +
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 ++
doc/man/Makefile.am | 4 +
doc/man/hpsa_lsmplugin.1.in | 50 ++++
packaging/libstoragemgmt.spec.in | 39 +++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 512 +++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 56 +++++
13 files changed, 729 insertions(+), 1 deletion(-)
create mode 100644 .gitattributes
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
--
1.8.3.1
Gris Ge
2015-03-14 13:33:02 UTC
Permalink
* New empty(no real function) plugin: HP SmartArray with URI 'hpsa://' and
no password required.

* The hpsa plugin will use HP binary tool -- hpssacli to control localhost
HP SmartArray cards. Hence root privilege required.

* This patch contains:
1. Autotools update for new plugin.
2. RPM SPEC file update for new plugin package:
libstoragemgmt-hpsa-plugin (Fedora/RHEL)
libstoragemgmt1-hpsa-plugin (OpenSuSE)
3. New manpage: hpsa_lsmplugin.1
4. The lsm plugin configuration file for root privilege requirement.

Signed-off-by: Gris Ge <***@redhat.com>
---
config/Makefile.am | 5 ++++
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 +++++++++++
doc/man/Makefile.am | 4 ++++
doc/man/hpsa_lsmplugin.1.in | 50 ++++++++++++++++++++++++++++++++++++++
packaging/libstoragemgmt.spec.in | 39 ++++++++++++++++++++++++++++++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +++++++
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 52 ++++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 ++++++++++++++++++++++++++++
plugin/hpsa/utils.py | 16 +++++++++++++
12 files changed, 228 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py

diff --git a/config/Makefile.am b/config/Makefile.am
index 70ab97b..39fb224 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -11,3 +11,8 @@ if WITH_MEGARAID
pluginconf_DATA += pluginconf.d/megaraid.conf
EXTRA_DIST += pluginconf.d/megaraid.conf
endif
+
+if WITH_HPSA
+pluginconf_DATA += pluginconf.d/hpsa.conf
+EXTRA_DIST += pluginconf.d/hpsa.conf
+endif
diff --git a/config/pluginconf.d/hpsa.conf b/config/pluginconf.d/hpsa.conf
new file mode 100644
index 0000000..35b52d4
--- /dev/null
+++ b/config/pluginconf.d/hpsa.conf
@@ -0,0 +1 @@
+require-root-privilege = true;
diff --git a/configure.ac b/configure.ac
index db657bb..9315f35 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,6 +193,18 @@ AC_ARG_WITH([megaraid],
AM_CONDITIONAL([WITH_MEGARAID], [test "x$with_megaraid" = "xyes"])

dnl ==========================================================================
+dnl Add option '--without-hpsa' to exclude hpsa plugin.
+dnl ==========================================================================
+
+AC_ARG_WITH([hpsa],
+ [AS_HELP_STRING([--without-hpsa],
+ [Do not build the HP SmartArray plugin])],
+ [],
+ [with_hpsa=yes])
+
+AM_CONDITIONAL([WITH_HPSA], [test "x$with_hpsa" = "xyes"])
+
+dnl ==========================================================================
dnl Check for libconfig as it is needed for lsmd daemon
dnl ==========================================================================
PKG_CHECK_MODULES(
@@ -221,6 +233,7 @@ AC_OUTPUT(libstoragemgmt.pc \
plugin/Makefile \
plugin/simc/Makefile \
plugin/megaraid/Makefile \
+ plugin/hpsa/Makefile \
daemon/Makefile \
config/Makefile \
doc/Makefile \
@@ -229,6 +242,7 @@ AC_OUTPUT(libstoragemgmt.pc \
doc/doxygen.conf \
doc/man/lsmd.conf.5 \
doc/man/megaraid_lsmplugin.1 \
+ doc/man/hpsa_lsmplugin.1 \
tools/Makefile \
tools/udev/Makefile \
tools/lsmcli/Makefile \
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
index ccbcb38..19b536c 100644
--- a/doc/man/Makefile.am
+++ b/doc/man/Makefile.am
@@ -4,4 +4,8 @@ if WITH_MEGARAID
notrans_dist_man1_MANS += megaraid_lsmplugin.1
endif

+if WITH_HPSA
+notrans_dist_man1_MANS += hpsa_lsmplugin.1
+endif
+
notrans_dist_man5_MANS = lsmd.conf.5.in
diff --git a/doc/man/hpsa_lsmplugin.1.in b/doc/man/hpsa_lsmplugin.1.in
new file mode 100644
index 0000000..79ff565
--- /dev/null
+++ b/doc/man/hpsa_lsmplugin.1.in
@@ -0,0 +1,50 @@
+.TH hpsa_lsmplugin "1" "March 2015" "hpsa_lsmplugin @VERSION@" "libStorageMgmt"
+.SH NAME
+hpsa_lsmplugin -- LibstorageMgmt HP SmartArray plugin
+
+.SH DESCRIPTION
+LibstorageMgmt hpsa plugin allows user to manage HP SmartArray via vendor
+tool \fBhpssacli\fR[1].
+The 'hpsa_lsmplugin' executable file is for libStorageMgmt
+daemon to execute when client user specifies hpsa plugin in the URI.
+
+.SH URI
+To use this plugin, users should set their URI to this format:
+.nf
+
+ \fBhpsa://\fR
+ or
+ \fBhpsa://?hpssacli=<path_of_hpssacli>\fR
+
+.fi
+
+.TP hpssacli
+The 'hpssacli' URI parameter is used to specified the path of hpssacli tool.
+By default, this plugin will try these paths used by hpssacli rpm:
+\fB/usr/sbin/hpssacli\fR and \fB/opt/hp/hpssacli/bld/hpssacli\fR.
+
+.SH ROOT PRIVILEGE
+This plugin requires both \fBlsmd\fR daemon and API client running as root
+user. Please check manpage \fIlsmd.conf (5)\fR for detail.
+
+.SH SUPPORTED HARDWARES
+Please refer to HP website for hardware support status of hpssacli.
+Detailed support status can be queried via:
+
+ * \fBlsm.Client.capabilities()\fR (Python API)
+ * \fBlsm_capabilities()\fR (C API)
+ * \fBlsmcli capabilities\fR (lsmcli command line).
+
+.SH FIREWALL RULES
+This plugin only execute \fBhpssacli\fR on localhost. No network connection
+required.
+
+.SH SEE ALSO
+\fIlsmcli\fR(1), \fIlsmd\fR(1), [1]http://downloads.linux.hp.com/SDR/project/spp/
+
+.SH BUGS
+Please report bugs to
+\fI<libstoragemgmt-***@lists.sourceforge.net>\fR
+
+.SH AUTHOR
+Gris Ge \fI<***@redhat.com>\fR
diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index 8b84105..782fd9b 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -1,5 +1,6 @@
%bcond_with rest_api
%bcond_without megaraid
+%bcond_without hpsa

# Use one-line macro for OBS workaround:
# https://bugzilla.novell.com/show_bug.cgi?id=864323
@@ -9,6 +10,9 @@
%{?_with_megaraid: %global with_megaraid 1 }
%{?_without_megaraid: %global with_megaraid 0 }

+%{?_with_hpsa: %global with_hpsa 1 }
+%{?_without_hpsa: %global with_hpsa 0 }
+
%define libsoname libstoragemgmt

%if 0%{?suse_version} || 0%{?fedora} >= 15 || 0%{?rhel} >= 7
@@ -224,6 +228,17 @@ The %{libstoragemgmt}-megaraid-plugin package contains the plugin for LSI
MegaRAID storage management via storcli.
%endif

+%if 0%{?with_hpsa}
+%package -n %{libstoragemgmt}-hpsa-plugin
+Summary: Files for HP SmartArray support for %{libstoragemgmt}
+Group: System Environment/Libraries
+Requires: %{libstoragemgmt}%{?_isa} = %{version}-%{release}
+BuildArch: noarch
+
+%description -n %{libstoragemgmt}-hpsa-plugin
+The %{libstoragemgmt}-hpsa-plugin package contains the plugin for HP
+SmartArray storage management via hpssacli.
+%endif

%prep
%setup -q
@@ -238,6 +253,9 @@ MegaRAID storage management via storcli.
%if 0%{?with_megaraid} != 1
--without-megaraid \
%endif
+%if 0%{?with_hpsa} != 1
+ --without-hpsa \
+%endif
--disable-static

V=1 make %{?_smp_mflags}
@@ -443,6 +461,15 @@ if [ $1 -eq 0 ]; then
fi
%endif

+%if 0%{?with_hpsa}
+%postun -n %{libstoragemgmt}-hpsa-plugin
+if [ $1 -eq 0 ]; then
+ # Remove
+ /usr/bin/systemctl try-restart libstoragemgmt.service \
+ >/dev/null 2>&1 || :
+fi
+%endif
+
%files -n %{libstoragemgmt}
%defattr(-,root,root,-)
%doc README COPYING.LIB
@@ -550,6 +577,18 @@ fi
%{_mandir}/man1/megaraid_lsmplugin.1*
%endif

+%if 0%{?with_hpsa}
+%files -n %{libstoragemgmt}-hpsa-plugin
+%defattr(-,root,root,-)
+%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*
+%endif
+
%files udev
%defattr(-,root,root,-)
%{udev_dir}/udev/scan-scsi-target
diff --git a/plugin/Makefile.am b/plugin/Makefile.am
index f9146a8..3c35fa4 100644
--- a/plugin/Makefile.am
+++ b/plugin/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS=simc megaraid
+SUBDIRS=simc megaraid hpsa

plugindir = $(pythondir)/lsm/plugin

diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
new file mode 100644
index 0000000..3076cbc
--- /dev/null
+++ b/plugin/hpsa/Makefile.am
@@ -0,0 +1,8 @@
+if WITH_HPSA
+plugindir = $(pythondir)/lsm/plugin
+hpsadir = $(plugindir)/hpsa
+
+hpsa_PYTHON = __init__.py hpsa.py utils.py
+
+dist_bin_SCRIPTS= hpsa_lsmplugin
+endif
diff --git a/plugin/hpsa/__init__.py b/plugin/hpsa/__init__.py
new file mode 100644
index 0000000..61332b4
--- /dev/null
+++ b/plugin/hpsa/__init__.py
@@ -0,0 +1 @@
+from lsm.plugin.hpsa.hpsa import SmartArray
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
new file mode 100644
index 0000000..46a093e
--- /dev/null
+++ b/plugin/hpsa/hpsa.py
@@ -0,0 +1,52 @@
+# 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 <***@redhat.com>
+
+from lsm import (
+ IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber)
+
+
+class SmartArray(IPlugin):
+ def plugin_register(self, uri, password, timeout, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_unregister(self, flags=Client.FLAG_RSVD):
+ pass
+
+ def job_status(self, job_id, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def job_free(self, job_id, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_info(self, flags=Client.FLAG_RSVD):
+ return "HP SmartArray Plugin", VERSION
+
+ def time_out_set(self, ms, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def time_out_get(self, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ 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 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/hpsa_lsmplugin b/plugin/hpsa/hpsa_lsmplugin
new file mode 100755
index 0000000..07230f5
--- /dev/null
+++ b/plugin/hpsa/hpsa_lsmplugin
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+# 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: tasleson
+# Gris Ge <***@redhat.com>
+
+import sys
+import syslog
+import traceback
+
+try:
+ from lsm import PluginRunner
+ from lsm.plugin.hpsa import SmartArray
+
+ if __name__ == '__main__':
+ PluginRunner(SmartArray, sys.argv).run()
+except Exception:
+ #This should be quite rare, but when it does happen this is pretty
+ #key in understanding what happened, especially when it happens when
+ #running from the daemon.
+ msg = str(traceback.format_exc())
+ syslog.syslog(syslog.LOG_ERR, msg)
+ sys.stderr.write(msg)
+ sys.exit(1)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
new file mode 100644
index 0000000..8574ec8
--- /dev/null
+++ b/plugin/hpsa/utils.py
@@ -0,0 +1,16 @@
+## 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 <***@redhat.com>
--
1.8.3.1
Gris Ge
2015-03-14 13:33:03 UTC
Permalink
* 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.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 194 +++++++++++++++++++++++++++++++++++++++++++++++++--
plugin/hpsa/utils.py | 32 +++++++++
2 files changed, 222 insertions(+), 4 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 46a093e..a02241c 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -15,38 +15,224 @@
#
# Author: Gris Ge <***@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:
+ 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]
+
+ 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
index 8574ec8..67734b0 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -14,3 +14,35 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Author: Gris Ge <***@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
Tony Asleson
2015-03-16 20:25:49 UTC
Permalink
Comments below.

Thanks,
Tony
Post by Gris Ge
hpssacli ctrl all show detail
hpssacli ctrl all show status
* cmd_exec()
# Execute given commands and return STDOUT.
* ExecError
# Holding command failure output and return code.
* _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.
---
plugin/hpsa/hpsa.py | 194 +++++++++++++++++++++++++++++++++++++++++++++++++--
plugin/hpsa/utils.py | 32 +++++++++
2 files changed, 222 insertions(+), 4 deletions(-)
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 46a093e..a02241c 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -15,38 +15,224 @@
#
+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
+
+
+ return method(*args, **kwargs)
+ raise
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "Expected key missing from SmartArray hpssacli output:%s" %
+ key_error)
+ raise LsmError(ErrorNumber.PLUGIN_BUG, str(exec_error))
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "Got unexpected error %s" % common_error)
+
+ return _wrapper
+
+
+ """
+ Base on data of "hpssacli ctrl all show status"
+ """
+ status_info = ''
+ status = System.STATUS_UNKNOWN
+ check_list = [
+ 'Controller Status', 'Cache Status', 'Battery/Capacitor Status']
+ # TODO(Gris Ge): Beg HP for possible values
+ status = System.STATUS_OTHER
+ status_info += hp_ctrl_status[key_name]
+
+ status = System.STATUS_OK
+
+ return status, status_info
+
+
+ """
+ Got a output string of hpssacli to dictionary(nested).
+ """
+ output_lines = [l for l in output.split("\n") if l]
+
+ 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
+ }
+
+ cur_line = output_lines[line_num]
+ continue
+ nxt_line = ''
+ 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]
+
+ nxt_line_splitted = nxt_line.split(": ")
+ new_data = []
+ new_data = {}
+
+ cur_data_pointer[cur_line.lstrip()] = new_data
+ indent_2_data[nxt_indent_count] = new_data
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_parse_hpssacli_output(): Unexpected line '%s%s\n'" %
+ cur_line, nxt_line)
Missing '()' around the arguments for the format string
Post by Gris Ge
+ raise Exception("not a list: '%s'" % cur_line)
+ cur_data_pointer.append(cur_line.lstrip())
^^^^^^
This line is treating cur_data_pointer as an array when in other cases
it's being used as a dictionary.
Post by Gris Ge
+ cur_data_pointer[cur_line_splitted[0].lstrip()] = \
+ ": ".join(cur_line_splitted[1:]).strip()
+ return data
+ _DEFAULT_BIN_PATHS = [
+ "/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
+
+ self._sacli_bin = None
+
+ """
+ Try _DEFAULT_MDADM_BIN_PATHS
+ """
+ self._sacli_bin = cur_path
+
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "SmartArray sacli is not installed correctly")
+
- pass
+ 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')
+ self._find_sacli()
+ self._sacli_exec(['version'], flag_convert=False)
+
pass
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
pass
return "HP SmartArray Plugin", VERSION
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
return Capabilities()
- raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+ """
+ If flag_convert is True, convert data into dict.
+ """
+ sacli_cmds.insert(0, self._sacli_bin)
+ output = cmd_exec(sacli_cmds)
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "hpssacli binary '%s' is not exist or executable." %
+ self._sacli_bin)
+ raise
+
+ return _parse_hpssacli_output(output)
+ return output
+
+ """
+ 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"])
+
+ 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
def pools(self, search_key=None, search_value=None,
raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
index 8574ec8..67734b0 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -14,3 +14,35 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
+
+import subprocess
+import os
+
+
+ """
+ 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()
+ raise ExecError(" ".join(cmds), errno, str_stdout, str_stderr)
+ return str_stdout
+
+
+ Exception.__init__(self, *args, **kwargs)
+ self.cmd = cmd
+ self.errno = errno
+ self.stdout = stdout
+ self.stderr = stderr
+
+ return "cmd: '%s', errno: %d, stdout: '%s', stderr: '%s'" % \
+ (self.cmd, self.errno, self.stdout, self.stderr)
Gris Ge
2015-03-19 12:57:15 UTC
Permalink
Post by Tony Asleson
Comments below.
Thanks,
Tony
Post by Gris Ge
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_parse_hpssacli_output(): Unexpected line '%s%s\n'" %
+ cur_line, nxt_line)
Missing '()' around the arguments for the format string
Thanks.
Post by Tony Asleson
Post by Gris Ge
+ raise Exception("not a list: '%s'" % cur_line)
+ cur_data_pointer.append(cur_line.lstrip())
^^^^^^
This line is treating cur_data_pointer as an array when in other cases
it's being used as a dictionary.
I was checked by previous line parsing. If next line does not contain
": ", current line will be used to create an array.

Best regards.
--
Gris Ge
Gris Ge
2015-03-20 06:50:57 UTC
Permalink
* The new hpsa plugin is based on HP binary tool -- hpssacli which
could be downloaded from:
http://downloads.linux.hp.com/SDR/project/spp/

* This patch set introduced these methods support to hpsa plugin:
* lsm.Client.systems()
* lsm.Client.pools()
* lsm.Client.volumes()
* lsm.Client.disks()
# With Disk.STATUS_FREE support also.
* lsm.Client.volume_raid_info()

* This patch set only coded and tested on 2 servers(Tony's and mine), a
automatic tests is running in our internal lab to test this plugin on more
HP SmartArray cards, will provide subsequent patch if found any bug.

Changes in V2:

* Patch 2/6:
* Fix missing '()' in _parse_hpssacli_output().
* Handle 'No controllers detected' error.

* Patch 6/6:
* Fix uninitialized variables in volume_raid_info().
* Add an extra check to raise PLUGIN_BUG when LD found with no PD found.

* Please be informed, RHEL 7.1 does not compatible with
hpssacli-2.0-23.0.x86_64 yet. Please use RHEL 6 or RHEL 7.0 instead.

Gris Ge (6):
New plugin: HP SmartArray Plugin.
HP SmartArray Plugin: Add systems() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.disks() support.
HP SmartArray Plugin: Add lsm.Client.volume_raid_info() support.

config/Makefile.am | 5 +
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 ++
doc/man/Makefile.am | 4 +
doc/man/hpsa_lsmplugin.1.in | 50 ++++
packaging/libstoragemgmt.spec.in | 39 +++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 527 +++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 56 +++++
12 files changed, 743 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
--
1.8.3.1
Gris Ge
2015-03-20 06:50:58 UTC
Permalink
* New empty(no real function) plugin: HP SmartArray with URI 'hpsa://' and
no password required.

* The hpsa plugin will use HP binary tool -- hpssacli to control localhost
HP SmartArray cards. Hence root privilege required.

* This patch contains:
1. Autotools update for new plugin.
2. RPM SPEC file update for new plugin package:
libstoragemgmt-hpsa-plugin (Fedora/RHEL)
libstoragemgmt1-hpsa-plugin (OpenSuSE)
3. New manpage: hpsa_lsmplugin.1
4. The lsm plugin configuration file for root privilege requirement.

Signed-off-by: Gris Ge <***@redhat.com>
---
config/Makefile.am | 5 ++++
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 +++++++++++
doc/man/Makefile.am | 4 ++++
doc/man/hpsa_lsmplugin.1.in | 50 ++++++++++++++++++++++++++++++++++++++
packaging/libstoragemgmt.spec.in | 39 ++++++++++++++++++++++++++++++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +++++++
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 52 ++++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 ++++++++++++++++++++++++++++
plugin/hpsa/utils.py | 16 +++++++++++++
12 files changed, 228 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py

diff --git a/config/Makefile.am b/config/Makefile.am
index 70ab97b..39fb224 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -11,3 +11,8 @@ if WITH_MEGARAID
pluginconf_DATA += pluginconf.d/megaraid.conf
EXTRA_DIST += pluginconf.d/megaraid.conf
endif
+
+if WITH_HPSA
+pluginconf_DATA += pluginconf.d/hpsa.conf
+EXTRA_DIST += pluginconf.d/hpsa.conf
+endif
diff --git a/config/pluginconf.d/hpsa.conf b/config/pluginconf.d/hpsa.conf
new file mode 100644
index 0000000..35b52d4
--- /dev/null
+++ b/config/pluginconf.d/hpsa.conf
@@ -0,0 +1 @@
+require-root-privilege = true;
diff --git a/configure.ac b/configure.ac
index db657bb..9315f35 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,6 +193,18 @@ AC_ARG_WITH([megaraid],
AM_CONDITIONAL([WITH_MEGARAID], [test "x$with_megaraid" = "xyes"])

dnl ==========================================================================
+dnl Add option '--without-hpsa' to exclude hpsa plugin.
+dnl ==========================================================================
+
+AC_ARG_WITH([hpsa],
+ [AS_HELP_STRING([--without-hpsa],
+ [Do not build the HP SmartArray plugin])],
+ [],
+ [with_hpsa=yes])
+
+AM_CONDITIONAL([WITH_HPSA], [test "x$with_hpsa" = "xyes"])
+
+dnl ==========================================================================
dnl Check for libconfig as it is needed for lsmd daemon
dnl ==========================================================================
PKG_CHECK_MODULES(
@@ -221,6 +233,7 @@ AC_OUTPUT(libstoragemgmt.pc \
plugin/Makefile \
plugin/simc/Makefile \
plugin/megaraid/Makefile \
+ plugin/hpsa/Makefile \
daemon/Makefile \
config/Makefile \
doc/Makefile \
@@ -229,6 +242,7 @@ AC_OUTPUT(libstoragemgmt.pc \
doc/doxygen.conf \
doc/man/lsmd.conf.5 \
doc/man/megaraid_lsmplugin.1 \
+ doc/man/hpsa_lsmplugin.1 \
tools/Makefile \
tools/udev/Makefile \
tools/lsmcli/Makefile \
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
index ccbcb38..19b536c 100644
--- a/doc/man/Makefile.am
+++ b/doc/man/Makefile.am
@@ -4,4 +4,8 @@ if WITH_MEGARAID
notrans_dist_man1_MANS += megaraid_lsmplugin.1
endif

+if WITH_HPSA
+notrans_dist_man1_MANS += hpsa_lsmplugin.1
+endif
+
notrans_dist_man5_MANS = lsmd.conf.5.in
diff --git a/doc/man/hpsa_lsmplugin.1.in b/doc/man/hpsa_lsmplugin.1.in
new file mode 100644
index 0000000..79ff565
--- /dev/null
+++ b/doc/man/hpsa_lsmplugin.1.in
@@ -0,0 +1,50 @@
+.TH hpsa_lsmplugin "1" "March 2015" "hpsa_lsmplugin @VERSION@" "libStorageMgmt"
+.SH NAME
+hpsa_lsmplugin -- LibstorageMgmt HP SmartArray plugin
+
+.SH DESCRIPTION
+LibstorageMgmt hpsa plugin allows user to manage HP SmartArray via vendor
+tool \fBhpssacli\fR[1].
+The 'hpsa_lsmplugin' executable file is for libStorageMgmt
+daemon to execute when client user specifies hpsa plugin in the URI.
+
+.SH URI
+To use this plugin, users should set their URI to this format:
+.nf
+
+ \fBhpsa://\fR
+ or
+ \fBhpsa://?hpssacli=<path_of_hpssacli>\fR
+
+.fi
+
+.TP hpssacli
+The 'hpssacli' URI parameter is used to specified the path of hpssacli tool.
+By default, this plugin will try these paths used by hpssacli rpm:
+\fB/usr/sbin/hpssacli\fR and \fB/opt/hp/hpssacli/bld/hpssacli\fR.
+
+.SH ROOT PRIVILEGE
+This plugin requires both \fBlsmd\fR daemon and API client running as root
+user. Please check manpage \fIlsmd.conf (5)\fR for detail.
+
+.SH SUPPORTED HARDWARES
+Please refer to HP website for hardware support status of hpssacli.
+Detailed support status can be queried via:
+
+ * \fBlsm.Client.capabilities()\fR (Python API)
+ * \fBlsm_capabilities()\fR (C API)
+ * \fBlsmcli capabilities\fR (lsmcli command line).
+
+.SH FIREWALL RULES
+This plugin only execute \fBhpssacli\fR on localhost. No network connection
+required.
+
+.SH SEE ALSO
+\fIlsmcli\fR(1), \fIlsmd\fR(1), [1]http://downloads.linux.hp.com/SDR/project/spp/
+
+.SH BUGS
+Please report bugs to
+\fI<libstoragemgmt-***@lists.sourceforge.net>\fR
+
+.SH AUTHOR
+Gris Ge \fI<***@redhat.com>\fR
diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index 8b84105..782fd9b 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -1,5 +1,6 @@
%bcond_with rest_api
%bcond_without megaraid
+%bcond_without hpsa

# Use one-line macro for OBS workaround:
# https://bugzilla.novell.com/show_bug.cgi?id=864323
@@ -9,6 +10,9 @@
%{?_with_megaraid: %global with_megaraid 1 }
%{?_without_megaraid: %global with_megaraid 0 }

+%{?_with_hpsa: %global with_hpsa 1 }
+%{?_without_hpsa: %global with_hpsa 0 }
+
%define libsoname libstoragemgmt

%if 0%{?suse_version} || 0%{?fedora} >= 15 || 0%{?rhel} >= 7
@@ -224,6 +228,17 @@ The %{libstoragemgmt}-megaraid-plugin package contains the plugin for LSI
MegaRAID storage management via storcli.
%endif

+%if 0%{?with_hpsa}
+%package -n %{libstoragemgmt}-hpsa-plugin
+Summary: Files for HP SmartArray support for %{libstoragemgmt}
+Group: System Environment/Libraries
+Requires: %{libstoragemgmt}%{?_isa} = %{version}-%{release}
+BuildArch: noarch
+
+%description -n %{libstoragemgmt}-hpsa-plugin
+The %{libstoragemgmt}-hpsa-plugin package contains the plugin for HP
+SmartArray storage management via hpssacli.
+%endif

%prep
%setup -q
@@ -238,6 +253,9 @@ MegaRAID storage management via storcli.
%if 0%{?with_megaraid} != 1
--without-megaraid \
%endif
+%if 0%{?with_hpsa} != 1
+ --without-hpsa \
+%endif
--disable-static

V=1 make %{?_smp_mflags}
@@ -443,6 +461,15 @@ if [ $1 -eq 0 ]; then
fi
%endif

+%if 0%{?with_hpsa}
+%postun -n %{libstoragemgmt}-hpsa-plugin
+if [ $1 -eq 0 ]; then
+ # Remove
+ /usr/bin/systemctl try-restart libstoragemgmt.service \
+ >/dev/null 2>&1 || :
+fi
+%endif
+
%files -n %{libstoragemgmt}
%defattr(-,root,root,-)
%doc README COPYING.LIB
@@ -550,6 +577,18 @@ fi
%{_mandir}/man1/megaraid_lsmplugin.1*
%endif

+%if 0%{?with_hpsa}
+%files -n %{libstoragemgmt}-hpsa-plugin
+%defattr(-,root,root,-)
+%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*
+%endif
+
%files udev
%defattr(-,root,root,-)
%{udev_dir}/udev/scan-scsi-target
diff --git a/plugin/Makefile.am b/plugin/Makefile.am
index f9146a8..3c35fa4 100644
--- a/plugin/Makefile.am
+++ b/plugin/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS=simc megaraid
+SUBDIRS=simc megaraid hpsa

plugindir = $(pythondir)/lsm/plugin

diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
new file mode 100644
index 0000000..3076cbc
--- /dev/null
+++ b/plugin/hpsa/Makefile.am
@@ -0,0 +1,8 @@
+if WITH_HPSA
+plugindir = $(pythondir)/lsm/plugin
+hpsadir = $(plugindir)/hpsa
+
+hpsa_PYTHON = __init__.py hpsa.py utils.py
+
+dist_bin_SCRIPTS= hpsa_lsmplugin
+endif
diff --git a/plugin/hpsa/__init__.py b/plugin/hpsa/__init__.py
new file mode 100644
index 0000000..61332b4
--- /dev/null
+++ b/plugin/hpsa/__init__.py
@@ -0,0 +1 @@
+from lsm.plugin.hpsa.hpsa import SmartArray
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
new file mode 100644
index 0000000..46a093e
--- /dev/null
+++ b/plugin/hpsa/hpsa.py
@@ -0,0 +1,52 @@
+# 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 <***@redhat.com>
+
+from lsm import (
+ IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber)
+
+
+class SmartArray(IPlugin):
+ def plugin_register(self, uri, password, timeout, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_unregister(self, flags=Client.FLAG_RSVD):
+ pass
+
+ def job_status(self, job_id, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def job_free(self, job_id, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_info(self, flags=Client.FLAG_RSVD):
+ return "HP SmartArray Plugin", VERSION
+
+ def time_out_set(self, ms, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def time_out_get(self, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ 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 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/hpsa_lsmplugin b/plugin/hpsa/hpsa_lsmplugin
new file mode 100755
index 0000000..07230f5
--- /dev/null
+++ b/plugin/hpsa/hpsa_lsmplugin
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+# 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: tasleson
+# Gris Ge <***@redhat.com>
+
+import sys
+import syslog
+import traceback
+
+try:
+ from lsm import PluginRunner
+ from lsm.plugin.hpsa import SmartArray
+
+ if __name__ == '__main__':
+ PluginRunner(SmartArray, sys.argv).run()
+except Exception:
+ #This should be quite rare, but when it does happen this is pretty
+ #key in understanding what happened, especially when it happens when
+ #running from the daemon.
+ msg = str(traceback.format_exc())
+ syslog.syslog(syslog.LOG_ERR, msg)
+ sys.stderr.write(msg)
+ sys.exit(1)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
new file mode 100644
index 0000000..8574ec8
--- /dev/null
+++ b/plugin/hpsa/utils.py
@@ -0,0 +1,16 @@
+## 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 <***@redhat.com>
--
1.8.3.1
Gris Ge
2015-03-20 06:50:59 UTC
Permalink
* 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.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 199 +++++++++++++++++++++++++++++++++++++++++++++++++--
plugin/hpsa/utils.py | 32 +++++++++
2 files changed, 227 insertions(+), 4 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 46a093e..f7fa88f 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -15,38 +15,229 @@
#
# Author: Gris Ge <***@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]
+
+ 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
index 8574ec8..67734b0 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -14,3 +14,35 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Author: Gris Ge <***@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
Gris Ge
2015-03-20 06:51:00 UTC
Permalink
* Treating HP SmartArray 'Array' term as LSM Pool.

* Depending on output of 'hpssacli ctrl all show config detail'.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 74 insertions(+), 2 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index f7fa88f..e04134d 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -17,10 +17,11 @@

import os
import errno
+import re

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System)
+ System, Pool, size_human_2_size_bytes, search_property)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError

@@ -130,6 +131,38 @@ def _parse_hpssacli_output(output):
return data


+def _hp_size_to_lsm(hp_size):
+ """
+ HP Using 'TB, GB, MB, KB' and etc, for LSM, they are 'TiB' and etc.
+ Return int of block bytes
+ """
+ re_regex = re.compile("^([0-9.]+) +([EPTGMK])B$")
+ re_match = re_regex.match(hp_size)
+ if re_match:
+ return size_human_2_size_bytes(
+ "%s%siB" % (re_match.group(1), re_match.group(2)))
+
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_hp_size_to_lsm(): Got unexpected HP size string %s" %
+ hp_size)
+
+
+def _pool_status_of(hp_array):
+ """
+ Return (status, status_info)
+ """
+ if hp_array['Status'] == 'OK':
+ return Pool.STATUS_OK, ''
+ else:
+ # TODO(Gris Ge): Try degrade a RAID or fail a RAID.
+ return Pool.STATUS_OTHER, hp_array['Status']
+
+
+def _pool_id_of(sys_id, array_name):
+ return "%s:%s" % (sys_id, array_name.replace(' ', ''))
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -237,7 +270,46 @@ def systems(self, flags=0):

return rc_lsm_syss

+ @staticmethod
+ def _hp_array_to_lsm_pool(hp_array, array_name, sys_id, ctrl_num):
+ pool_id = _pool_id_of(sys_id, array_name)
+ name = array_name
+ elem_type = Pool.ELEMENT_TYPE_VOLUME | Pool.ELEMENT_TYPE_VOLUME_FULL
+ unsupported_actions = 0
+ # TODO(Gris Ge): HP does not provide a precise number of bytes.
+ free_space = _hp_size_to_lsm(hp_array['Unused Space'])
+ total_space = free_space
+ for key_name in hp_array.keys():
+ if key_name.startswith('Logical Drive'):
+ total_space += _hp_size_to_lsm(hp_array[key_name]['Size'])
+
+ (status, status_info) = _pool_status_of(hp_array)
+
+ plugin_data = "%s:%s" % (
+ ctrl_num, array_name[len("Array: "):])
+
+ return Pool(
+ pool_id, name, elem_type, unsupported_actions,
+ total_space, free_space, status, status_info,
+ sys_id, plugin_data)
+
@_handle_errors
def pools(self, search_key=None, search_value=None,
flags=Client.FLAG_RSVD):
- raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_pools = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ lsm_pools.append(
+ SmartArray._hp_array_to_lsm_pool(
+ ctrl_data[key_name], key_name, sys_id, ctrl_num))
+
+ return search_property(lsm_pools, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-20 06:51:01 UTC
Permalink
* Treating HP SmartArray logical drive as LSM volume.

* Depend on output of command "hpssacli ctrl all show config detail".

* New method of utils.py:
* file_read()
# Simply use 'cat' command to do file read which provide better
# error handling using ExecError.

* The block size and block count is read from linux sysfs as hpssacli
provide the sdX name for each logical drive.
Faillback to 512 and calculated from other output entry.

* Include sdX name if found in Volume.name.

* Set Capabilities.VOLUMES.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---
plugin/hpsa/utils.py | 8 ++++++
2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index e04134d..715d28e 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,9 +21,9 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property)
+ System, Pool, size_human_2_size_bytes, search_property, Volume)

-from lsm.plugin.hpsa.utils import cmd_exec, ExecError
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read


def _handle_errors(method):
@@ -222,7 +222,14 @@ def time_out_get(self, flags=Client.FLAG_RSVD):

@_handle_errors
def capabilities(self, system, flags=Client.FLAG_RSVD):
- return Capabilities()
+ cur_lsm_syss = self.systems()
+ if system.id not in list(s.id for s in cur_lsm_syss):
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_SYSTEM,
+ "System not found")
+ cap = Capabilities()
+ cap.set(Capabilities.VOLUMES)
+ return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
"""
@@ -313,3 +320,59 @@ def pools(self, search_key=None, search_value=None,
ctrl_data[key_name], key_name, sys_id, ctrl_num))

return search_property(lsm_pools, search_key, search_value)
+
+ @staticmethod
+ def _hp_ld_to_lsm_vol(hp_ld, pool_id, sys_id, ctrl_num, array_num,
+ hp_ld_name):
+ ld_num = hp_ld_name[len("Logical Drive: "):]
+ vpd83 = hp_ld['Unique Identifier'].lower()
+ # No document or command output indicate block size
+ # of volume. So we try to read from linux kernel, if failed
+ # try 512 and roughly calculate the sector count.
+ regex_match = re.compile("/dev/(sd[a-z]+)").search(hp_ld['Disk Name'])
+ vol_name = hp_ld_name
+ if regex_match:
+ sd_name = regex_match.group(1)
+ block_size = int(file_read(
+ "/sys/block/%s/queue/logical_block_size" % sd_name))
+ num_of_blocks = int(file_read("/sys/block/%s/size" % sd_name))
+ vol_name += ": /dev/%s" % sd_name
+ else:
+ block_size = 512
+ num_of_blocks = int(_hp_size_to_lsm(hp_ld['Size']) / block_size)
+
+ plugin_data = "%s:%s:%s" % (ctrl_num, array_num, ld_num)
+
+ # HP SmartArray does not allow disabling volume.
+ return Volume(
+ vpd83, vol_name, vpd83, block_size, num_of_blocks,
+ Volume.ADMIN_STATE_ENABLED, sys_id, pool_id, plugin_data)
+
+ @_handle_errors
+ def volumes(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_vols = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ ctrl_num = ctrl_data['Slot']
+ sys_id = ctrl_data['Serial Number']
+ for key_name in ctrl_data.keys():
+ if not key_name.startswith("Array:"):
+ continue
+ pool_id = _pool_id_of(sys_id, key_name)
+ array_num = key_name[len('Array: '):]
+ for array_key_name in ctrl_data[key_name].keys():
+ if not array_key_name.startswith("Logical Drive"):
+ continue
+ lsm_vols.append(
+ SmartArray._hp_ld_to_lsm_vol(
+ ctrl_data[key_name][array_key_name],
+ pool_id, sys_id, ctrl_num, array_num,
+ array_key_name))
+
+ return search_property(lsm_vols, search_key, search_value)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
index 67734b0..52670a0 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -35,6 +35,14 @@ def cmd_exec(cmds):
return str_stdout


+def file_read(file_path):
+ """
+ Read file and return string of file content.
+ Use 'cat' command to better utilize the ExecError.
+ """
+ return cmd_exec(['cat', file_path])
+
+
class ExecError(Exception):
def __init__(self, cmd, errno, stdout, stderr, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
--
1.8.3.1
Gris Ge
2015-03-20 06:51:02 UTC
Permalink
* Treating HP SmartArray physical disk as LSM disk.

* Depending on output of command "hpssacli ctrl all show config detail"

* Only support Disk.STATUS_FREE, Disk.STATUS_OK and Disk.STATUS_OTHER.
# Seeking server to investigate on spare disk and faulty disks.

* Including physical disk location in Disk.name.

* Set Capabilities.DISKS.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 715d28e..8634305 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,7 +21,7 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property, Volume)
+ System, Pool, size_human_2_size_bytes, search_property, Volume, Disk)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read

@@ -163,6 +163,31 @@ def _pool_id_of(sys_id, array_name):
return "%s:%s" % (sys_id, array_name.replace(' ', ''))


+def _disk_type_of(hp_disk):
+ disk_interface = hp_disk['Interface Type']
+ if disk_interface == 'SATA':
+ return Disk.TYPE_SATA
+ elif disk_interface == 'Solid State SATA':
+ return Disk.TYPE_SSD
+ elif disk_interface == 'SAS':
+ return Disk.TYPE_SAS
+
+ return Disk.TYPE_UNKNOWN
+
+
+def _disk_status_of(hp_disk, flag_free):
+ # TODO(Gris Ge): Need more document or test for non-OK disks.
+ if hp_disk['Status'] == 'OK':
+ disk_status = Disk.STATUS_OK
+ else:
+ disk_status = Disk.STATUS_OTHER
+
+ if flag_free:
+ disk_status |= Disk.STATUS_FREE
+
+ return disk_status
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -229,6 +254,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
"System not found")
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
+ cap.set(Capabilities.DISKS)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -376,3 +402,55 @@ def volumes(self, search_key=None, search_value=None,
array_key_name))

return search_property(lsm_vols, search_key, search_value)
+
+ @staticmethod
+ def _hp_disk_to_lsm_disk(hp_disk, sys_id, ctrl_num, key_name,
+ flag_free=False):
+ disk_id = hp_disk['Serial Number']
+ disk_num = key_name[len("physicaldrive "):]
+ disk_name = "%s %s" % (hp_disk['Model'], disk_num)
+ disk_type = _disk_type_of(hp_disk)
+ blk_size = int(hp_disk['Native Block Size'])
+ blk_count = int(_hp_size_to_lsm(hp_disk['Size']) / blk_size)
+ status = _disk_status_of(hp_disk, flag_free)
+ plugin_data = "%s:%s" % (ctrl_num, disk_num)
+
+ return Disk(
+ disk_id, disk_name, disk_type, blk_size, blk_count,
+ status, sys_id, plugin_data)
+
+ @_handle_errors
+ def disks(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ # TODO(Gris Ge): Need real test on spare disk and free disk.
+ # The free disks is purely base on HP document.
+ rc_lsm_disks = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ if key_name == 'unassigned':
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ return search_property(rc_lsm_disks, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-20 06:51:03 UTC
Permalink
* Depending on output of command 'hpssacli ctrl slot=X show config detail'

* The hpssacli provide strip size and stripe size which is min_io_size and
opt_io_size. They are identical to values of Linux sysfs equivalent
entries.

* Set Capabilities.VOLUME_RAID_INFO.

Changes in V2:

* Initialize these variables in volume_raid_info():
strip_size, stripe_size, raid_type

* Add an extra check to raise PLUGIN_BUG just in case we got LD but no PD
entry when checking raid disk members in volume_raid_info().

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

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 8634305..721b64b 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -188,6 +188,28 @@ def _disk_status_of(hp_disk, flag_free):
return disk_status


+def _hp_raid_type_to_lsm(hp_ld):
+ """
+ Based on this property:
+ Fault Tolerance: 0/1/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ if hp_raid_level == '0':
+ return Volume.RAID_TYPE_RAID0
+ elif hp_raid_level == '1':
+ # TODO(Gris Ge): Investigate whether HP has 4 disks RAID 1.
+ # In LSM, that's RAID10.
+ return Volume.RAID_TYPE_RAID1
+ elif hp_raid_level == '5':
+ return Volume.RAID_TYPE_RAID5
+ elif hp_raid_level == '6':
+ return Volume.RAID_TYPE_RAID6
+ elif hp_raid_level == '1+0':
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -255,6 +277,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUME_RAID_INFO)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -454,3 +477,51 @@ def disks(self, search_key=None, search_value=None,
flag_free=False))

return search_property(rc_lsm_disks, search_key, search_value)
+
+ @_handle_errors
+ def volume_raid_info(self, volume, flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl slot=0 show config detail
+ """
+ if not volume.plugin_data:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+ ctrl_data = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"]
+ ).values()[0]
+
+ disk_count = 0
+ strip_size = Volume.STRIP_SIZE_UNKNOWN
+ stripe_size = Volume.OPT_IO_SIZE_UNKNOWN
+ raid_type = Volume.RAID_TYPE_UNKNOWN
+ for key_name in ctrl_data.keys():
+ if key_name != "Array: %s" % array_num:
+ continue
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name == "Logical Drive: %s" % ld_num:
+ hp_ld = ctrl_data[key_name][array_key_name]
+ raid_type = _hp_raid_type_to_lsm(hp_ld)
+ strip_size = _hp_size_to_lsm(hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(hp_ld['Full Stripe Size'])
+ elif array_key_name.startswith("physicaldrive"):
+ hp_disk = ctrl_data[key_name][array_key_name]
+ if hp_disk['Drive Type'] == 'Data Drive':
+ disk_count += 1
+
+ if disk_count == 0:
+ if strip_size == Volume.STRIP_SIZE_UNKNOWN:
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "volume_raid_info(): Got logical drive %s entry, " %
+ ld_num + "but no physicaldrive entry: %s" %
+ ctrl_data.items())
+
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
--
1.8.3.1
Tony Asleson
2015-03-20 17:17:12 UTC
Permalink
Comments left on github for this patch set.

Regards,
Tony
Post by Gris Ge
* The new hpsa plugin is based on HP binary tool -- hpssacli which
http://downloads.linux.hp.com/SDR/project/spp/
* lsm.Client.systems()
* lsm.Client.pools()
* lsm.Client.volumes()
* lsm.Client.disks()
# With Disk.STATUS_FREE support also.
* lsm.Client.volume_raid_info()
* This patch set only coded and tested on 2 servers(Tony's and mine), a
automatic tests is running in our internal lab to test this plugin on more
HP SmartArray cards, will provide subsequent patch if found any bug.
* Fix missing '()' in _parse_hpssacli_output().
* Handle 'No controllers detected' error.
* Fix uninitialized variables in volume_raid_info().
* Add an extra check to raise PLUGIN_BUG when LD found with no PD found.
* Please be informed, RHEL 7.1 does not compatible with
hpssacli-2.0-23.0.x86_64 yet. Please use RHEL 6 or RHEL 7.0 instead.
New plugin: HP SmartArray Plugin.
HP SmartArray Plugin: Add systems() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.disks() support.
HP SmartArray Plugin: Add lsm.Client.volume_raid_info() support.
config/Makefile.am | 5 +
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 ++
doc/man/Makefile.am | 4 +
doc/man/hpsa_lsmplugin.1.in | 50 ++++
packaging/libstoragemgmt.spec.in | 39 +++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 527 +++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 56 +++++
12 files changed, 743 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
Gris Ge
2015-03-26 12:59:25 UTC
Permalink
* The new hpsa plugin is based on HP binary tool -- hpssacli which
could be downloaded from:
http://downloads.linux.hp.com/SDR/project/spp/

* This patch set introduced these methods support to hpsa plugin:
* lsm.Client.systems()
* lsm.Client.pools()
* lsm.Client.volumes()
* lsm.Client.disks()
# With Disk.STATUS_FREE support also.
* lsm.Client.volume_raid_info()

* This patch set only coded and tested on 2 servers(Tony's and mine), a
automatic tests is running in our internal lab to test this plugin on more
HP SmartArray cards, will provide subsequent patch if found any bug.

Changes in V2:

* Patch 2/6:
* Fix missing '()' in _parse_hpssacli_output().
* Handle 'No controllers detected' error.

* Patch 6/6:
* Fix uninitialized variables in volume_raid_info().
* Add an extra check to raise PLUGIN_BUG when LD found with no PD found.

* Please be informed, on RHEL 7.1, you need to manually run 'modprobe sg'
command before using this plugin.

Changes in V3:

* Moved 'utils.py' to patch 2/6.

* Patch 2/6:

Fixed a 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:'

* Patch 4/6: Changed file_read() to use python fileIO(open,read,close).

* Tested on HP SmartArray P420i with plugin_test.py(only got expected failure
for test_timeout).

Gris Ge (6):
New plugin: HP SmartArray Plugin.
HP SmartArray Plugin: Add systems() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.volumes() support.
HP SmartArray Plugin: Add lsm.Client.disks() support.
HP SmartArray Plugin: Add lsm.Client.volume_raid_info() support.

config/Makefile.am | 5 +
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 ++
doc/man/Makefile.am | 4 +
doc/man/hpsa_lsmplugin.1.in | 50 ++++
packaging/libstoragemgmt.spec.in | 39 +++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 528 +++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 58 +++++
12 files changed, 746 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
--
1.8.3.1
Gris Ge
2015-03-26 12:59:26 UTC
Permalink
* New empty(no real function) plugin: HP SmartArray with URI 'hpsa://' and
no password required.

* The hpsa plugin will use HP binary tool -- hpssacli to control localhost
HP SmartArray cards. Hence root privilege required.

* This patch contains:
1. Autotools update for new plugin.
2. RPM SPEC file update for new plugin package:
libstoragemgmt-hpsa-plugin (Fedora/RHEL)
libstoragemgmt1-hpsa-plugin (OpenSuSE)
3. New manpage: hpsa_lsmplugin.1
4. The lsm plugin configuration file for root privilege requirement.

Changes in V3(no changes in V2):

* Move the 'utils.py' files to next commit along with its rpm spec and
automake file update.

Signed-off-by: Gris Ge <***@redhat.com>
---
config/Makefile.am | 5 ++++
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 +++++++++++
doc/man/Makefile.am | 4 ++++
doc/man/hpsa_lsmplugin.1.in | 50 ++++++++++++++++++++++++++++++++++++++
packaging/libstoragemgmt.spec.in | 38 +++++++++++++++++++++++++++++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +++++++
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 52 ++++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 ++++++++++++++++++++++++++++
11 files changed, 211 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin

diff --git a/config/Makefile.am b/config/Makefile.am
index 70ab97b..39fb224 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -11,3 +11,8 @@ if WITH_MEGARAID
pluginconf_DATA += pluginconf.d/megaraid.conf
EXTRA_DIST += pluginconf.d/megaraid.conf
endif
+
+if WITH_HPSA
+pluginconf_DATA += pluginconf.d/hpsa.conf
+EXTRA_DIST += pluginconf.d/hpsa.conf
+endif
diff --git a/config/pluginconf.d/hpsa.conf b/config/pluginconf.d/hpsa.conf
new file mode 100644
index 0000000..35b52d4
--- /dev/null
+++ b/config/pluginconf.d/hpsa.conf
@@ -0,0 +1 @@
+require-root-privilege = true;
diff --git a/configure.ac b/configure.ac
index db657bb..9315f35 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,6 +193,18 @@ AC_ARG_WITH([megaraid],
AM_CONDITIONAL([WITH_MEGARAID], [test "x$with_megaraid" = "xyes"])

dnl ==========================================================================
+dnl Add option '--without-hpsa' to exclude hpsa plugin.
+dnl ==========================================================================
+
+AC_ARG_WITH([hpsa],
+ [AS_HELP_STRING([--without-hpsa],
+ [Do not build the HP SmartArray plugin])],
+ [],
+ [with_hpsa=yes])
+
+AM_CONDITIONAL([WITH_HPSA], [test "x$with_hpsa" = "xyes"])
+
+dnl ==========================================================================
dnl Check for libconfig as it is needed for lsmd daemon
dnl ==========================================================================
PKG_CHECK_MODULES(
@@ -221,6 +233,7 @@ AC_OUTPUT(libstoragemgmt.pc \
plugin/Makefile \
plugin/simc/Makefile \
plugin/megaraid/Makefile \
+ plugin/hpsa/Makefile \
daemon/Makefile \
config/Makefile \
doc/Makefile \
@@ -229,6 +242,7 @@ AC_OUTPUT(libstoragemgmt.pc \
doc/doxygen.conf \
doc/man/lsmd.conf.5 \
doc/man/megaraid_lsmplugin.1 \
+ doc/man/hpsa_lsmplugin.1 \
tools/Makefile \
tools/udev/Makefile \
tools/lsmcli/Makefile \
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
index ccbcb38..19b536c 100644
--- a/doc/man/Makefile.am
+++ b/doc/man/Makefile.am
@@ -4,4 +4,8 @@ if WITH_MEGARAID
notrans_dist_man1_MANS += megaraid_lsmplugin.1
endif

+if WITH_HPSA
+notrans_dist_man1_MANS += hpsa_lsmplugin.1
+endif
+
notrans_dist_man5_MANS = lsmd.conf.5.in
diff --git a/doc/man/hpsa_lsmplugin.1.in b/doc/man/hpsa_lsmplugin.1.in
new file mode 100644
index 0000000..79ff565
--- /dev/null
+++ b/doc/man/hpsa_lsmplugin.1.in
@@ -0,0 +1,50 @@
+.TH hpsa_lsmplugin "1" "March 2015" "hpsa_lsmplugin @VERSION@" "libStorageMgmt"
+.SH NAME
+hpsa_lsmplugin -- LibstorageMgmt HP SmartArray plugin
+
+.SH DESCRIPTION
+LibstorageMgmt hpsa plugin allows user to manage HP SmartArray via vendor
+tool \fBhpssacli\fR[1].
+The 'hpsa_lsmplugin' executable file is for libStorageMgmt
+daemon to execute when client user specifies hpsa plugin in the URI.
+
+.SH URI
+To use this plugin, users should set their URI to this format:
+.nf
+
+ \fBhpsa://\fR
+ or
+ \fBhpsa://?hpssacli=<path_of_hpssacli>\fR
+
+.fi
+
+.TP hpssacli
+The 'hpssacli' URI parameter is used to specified the path of hpssacli tool.
+By default, this plugin will try these paths used by hpssacli rpm:
+\fB/usr/sbin/hpssacli\fR and \fB/opt/hp/hpssacli/bld/hpssacli\fR.
+
+.SH ROOT PRIVILEGE
+This plugin requires both \fBlsmd\fR daemon and API client running as root
+user. Please check manpage \fIlsmd.conf (5)\fR for detail.
+
+.SH SUPPORTED HARDWARES
+Please refer to HP website for hardware support status of hpssacli.
+Detailed support status can be queried via:
+
+ * \fBlsm.Client.capabilities()\fR (Python API)
+ * \fBlsm_capabilities()\fR (C API)
+ * \fBlsmcli capabilities\fR (lsmcli command line).
+
+.SH FIREWALL RULES
+This plugin only execute \fBhpssacli\fR on localhost. No network connection
+required.
+
+.SH SEE ALSO
+\fIlsmcli\fR(1), \fIlsmd\fR(1), [1]http://downloads.linux.hp.com/SDR/project/spp/
+
+.SH BUGS
+Please report bugs to
+\fI<libstoragemgmt-***@lists.sourceforge.net>\fR
+
+.SH AUTHOR
+Gris Ge \fI<***@redhat.com>\fR
diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index 1ca0eea..9e24faa 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -1,5 +1,6 @@
%bcond_with rest_api
%bcond_without megaraid
+%bcond_without hpsa

# Use one-line macro for OBS workaround:
# https://bugzilla.novell.com/show_bug.cgi?id=864323
@@ -9,6 +10,9 @@
%{?_with_megaraid: %global with_megaraid 1 }
%{?_without_megaraid: %global with_megaraid 0 }

+%{?_with_hpsa: %global with_hpsa 1 }
+%{?_without_hpsa: %global with_hpsa 0 }
+
%define libsoname libstoragemgmt

%if 0%{?suse_version} || 0%{?fedora} >= 15 || 0%{?rhel} >= 7
@@ -224,6 +228,17 @@ The %{libstoragemgmt}-megaraid-plugin package contains the plugin for LSI
MegaRAID storage management via storcli.
%endif

+%if 0%{?with_hpsa}
+%package -n %{libstoragemgmt}-hpsa-plugin
+Summary: Files for HP SmartArray support for %{libstoragemgmt}
+Group: System Environment/Libraries
+Requires: %{libstoragemgmt}%{?_isa} = %{version}-%{release}
+BuildArch: noarch
+
+%description -n %{libstoragemgmt}-hpsa-plugin
+The %{libstoragemgmt}-hpsa-plugin package contains the plugin for HP
+SmartArray storage management via hpssacli.
+%endif

%prep
%setup -q
@@ -238,6 +253,9 @@ MegaRAID storage management via storcli.
%if 0%{?with_megaraid} != 1
--without-megaraid \
%endif
+%if 0%{?with_hpsa} != 1
+ --without-hpsa \
+%endif
--disable-static

V=1 make %{?_smp_mflags}
@@ -450,6 +468,15 @@ if [ $1 -eq 0 ]; then
fi
%endif

+%if 0%{?with_hpsa}
+%postun -n %{libstoragemgmt}-hpsa-plugin
+if [ $1 -eq 0 ]; then
+ # Remove
+ /usr/bin/systemctl try-restart libstoragemgmt.service \
+ >/dev/null 2>&1 || :
+fi
+%endif
+
%files -n %{libstoragemgmt}
%defattr(-,root,root,-)
%doc README COPYING.LIB
@@ -557,6 +584,17 @@ fi
%{_mandir}/man1/megaraid_lsmplugin.1*
%endif

+%if 0%{?with_hpsa}
+%files -n %{libstoragemgmt}-hpsa-plugin
+%defattr(-,root,root,-)
+%dir %{python_sitelib}/lsm/plugin/hpsa
+%{python_sitelib}/lsm/plugin/hpsa/__init__.*
+%{python_sitelib}/lsm/plugin/hpsa/hpsa.*
+%{_bindir}/hpsa_lsmplugin
+%{_sysconfdir}/lsm/pluginconf.d/hpsa.conf
+%{_mandir}/man1/hpsa_lsmplugin.1*
+%endif
+
%files udev
%defattr(-,root,root,-)
%{udev_dir}/udev/scan-scsi-target
diff --git a/plugin/Makefile.am b/plugin/Makefile.am
index f9146a8..3c35fa4 100644
--- a/plugin/Makefile.am
+++ b/plugin/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS=simc megaraid
+SUBDIRS=simc megaraid hpsa

plugindir = $(pythondir)/lsm/plugin

diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
new file mode 100644
index 0000000..46344c0
--- /dev/null
+++ b/plugin/hpsa/Makefile.am
@@ -0,0 +1,8 @@
+if WITH_HPSA
+plugindir = $(pythondir)/lsm/plugin
+hpsadir = $(plugindir)/hpsa
+
+hpsa_PYTHON = __init__.py hpsa.py
+
+dist_bin_SCRIPTS= hpsa_lsmplugin
+endif
diff --git a/plugin/hpsa/__init__.py b/plugin/hpsa/__init__.py
new file mode 100644
index 0000000..61332b4
--- /dev/null
+++ b/plugin/hpsa/__init__.py
@@ -0,0 +1 @@
+from lsm.plugin.hpsa.hpsa import SmartArray
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
new file mode 100644
index 0000000..46a093e
--- /dev/null
+++ b/plugin/hpsa/hpsa.py
@@ -0,0 +1,52 @@
+# 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 <***@redhat.com>
+
+from lsm import (
+ IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber)
+
+
+class SmartArray(IPlugin):
+ def plugin_register(self, uri, password, timeout, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_unregister(self, flags=Client.FLAG_RSVD):
+ pass
+
+ def job_status(self, job_id, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def job_free(self, job_id, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_info(self, flags=Client.FLAG_RSVD):
+ return "HP SmartArray Plugin", VERSION
+
+ def time_out_set(self, ms, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def time_out_get(self, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ 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 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/hpsa_lsmplugin b/plugin/hpsa/hpsa_lsmplugin
new file mode 100755
index 0000000..07230f5
--- /dev/null
+++ b/plugin/hpsa/hpsa_lsmplugin
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+# 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: tasleson
+# Gris Ge <***@redhat.com>
+
+import sys
+import syslog
+import traceback
+
+try:
+ from lsm import PluginRunner
+ from lsm.plugin.hpsa import SmartArray
+
+ if __name__ == '__main__':
+ PluginRunner(SmartArray, sys.argv).run()
+except Exception:
+ #This should be quite rare, but when it does happen this is pretty
+ #key in understanding what happened, especially when it happens when
+ #running from the daemon.
+ msg = str(traceback.format_exc())
+ syslog.syslog(syslog.LOG_ERR, msg)
+ sys.stderr.write(msg)
+ sys.exit(1)
--
1.8.3.1
Gris Ge
2015-03-26 12:59:27 UTC
Permalink
* 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 <***@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 <***@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 <***@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
Gris Ge
2015-03-26 12:59:28 UTC
Permalink
* Treating HP SmartArray 'Array' term as LSM Pool.

* Depending on output of 'hpssacli ctrl all show config detail'.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 74 insertions(+), 2 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 4e4b5ac..19f50dc 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -17,10 +17,11 @@

import os
import errno
+import re

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System)
+ System, Pool, size_human_2_size_bytes, search_property)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError

@@ -131,6 +132,38 @@ def _parse_hpssacli_output(output):
return data


+def _hp_size_to_lsm(hp_size):
+ """
+ HP Using 'TB, GB, MB, KB' and etc, for LSM, they are 'TiB' and etc.
+ Return int of block bytes
+ """
+ re_regex = re.compile("^([0-9.]+) +([EPTGMK])B$")
+ re_match = re_regex.match(hp_size)
+ if re_match:
+ return size_human_2_size_bytes(
+ "%s%siB" % (re_match.group(1), re_match.group(2)))
+
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_hp_size_to_lsm(): Got unexpected HP size string %s" %
+ hp_size)
+
+
+def _pool_status_of(hp_array):
+ """
+ Return (status, status_info)
+ """
+ if hp_array['Status'] == 'OK':
+ return Pool.STATUS_OK, ''
+ else:
+ # TODO(Gris Ge): Try degrade a RAID or fail a RAID.
+ return Pool.STATUS_OTHER, hp_array['Status']
+
+
+def _pool_id_of(sys_id, array_name):
+ return "%s:%s" % (sys_id, array_name.replace(' ', ''))
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -238,7 +271,46 @@ def systems(self, flags=0):

return rc_lsm_syss

+ @staticmethod
+ def _hp_array_to_lsm_pool(hp_array, array_name, sys_id, ctrl_num):
+ pool_id = _pool_id_of(sys_id, array_name)
+ name = array_name
+ elem_type = Pool.ELEMENT_TYPE_VOLUME | Pool.ELEMENT_TYPE_VOLUME_FULL
+ unsupported_actions = 0
+ # TODO(Gris Ge): HP does not provide a precise number of bytes.
+ free_space = _hp_size_to_lsm(hp_array['Unused Space'])
+ total_space = free_space
+ for key_name in hp_array.keys():
+ if key_name.startswith('Logical Drive'):
+ total_space += _hp_size_to_lsm(hp_array[key_name]['Size'])
+
+ (status, status_info) = _pool_status_of(hp_array)
+
+ plugin_data = "%s:%s" % (
+ ctrl_num, array_name[len("Array: "):])
+
+ return Pool(
+ pool_id, name, elem_type, unsupported_actions,
+ total_space, free_space, status, status_info,
+ sys_id, plugin_data)
+
@_handle_errors
def pools(self, search_key=None, search_value=None,
flags=Client.FLAG_RSVD):
- raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_pools = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ lsm_pools.append(
+ SmartArray._hp_array_to_lsm_pool(
+ ctrl_data[key_name], key_name, sys_id, ctrl_num))
+
+ return search_property(lsm_pools, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-26 12:59:29 UTC
Permalink
* Treating HP SmartArray logical drive as LSM volume.

* Depend on output of command "hpssacli ctrl all show config detail".

* New method of utils.py:
* file_read()
# Simply use 'cat' command to do file read which provide better
# error handling using ExecError.

* The block size and block count is read from linux sysfs as hpssacli
provide the sdX name for each logical drive.
Faillback to 512 and calculated from other output entry.

* Include sdX name if found in Volume.name.

* Set Capabilities.VOLUMES.

Changes in V3(no changes in v2):

* Changed file_read() to use python fileIO(open,read,close).

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---
plugin/hpsa/utils.py | 10 ++++++++
2 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 19f50dc..eb5f83b 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,9 +21,9 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property)
+ System, Pool, size_human_2_size_bytes, search_property, Volume)

-from lsm.plugin.hpsa.utils import cmd_exec, ExecError
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read


def _handle_errors(method):
@@ -223,7 +223,14 @@ def time_out_get(self, flags=Client.FLAG_RSVD):

@_handle_errors
def capabilities(self, system, flags=Client.FLAG_RSVD):
- return Capabilities()
+ cur_lsm_syss = self.systems()
+ if system.id not in list(s.id for s in cur_lsm_syss):
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_SYSTEM,
+ "System not found")
+ cap = Capabilities()
+ cap.set(Capabilities.VOLUMES)
+ return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
"""
@@ -314,3 +321,59 @@ def pools(self, search_key=None, search_value=None,
ctrl_data[key_name], key_name, sys_id, ctrl_num))

return search_property(lsm_pools, search_key, search_value)
+
+ @staticmethod
+ def _hp_ld_to_lsm_vol(hp_ld, pool_id, sys_id, ctrl_num, array_num,
+ hp_ld_name):
+ ld_num = hp_ld_name[len("Logical Drive: "):]
+ vpd83 = hp_ld['Unique Identifier'].lower()
+ # No document or command output indicate block size
+ # of volume. So we try to read from linux kernel, if failed
+ # try 512 and roughly calculate the sector count.
+ regex_match = re.compile("/dev/(sd[a-z]+)").search(hp_ld['Disk Name'])
+ vol_name = hp_ld_name
+ if regex_match:
+ sd_name = regex_match.group(1)
+ block_size = int(file_read(
+ "/sys/block/%s/queue/logical_block_size" % sd_name))
+ num_of_blocks = int(file_read("/sys/block/%s/size" % sd_name))
+ vol_name += ": /dev/%s" % sd_name
+ else:
+ block_size = 512
+ num_of_blocks = int(_hp_size_to_lsm(hp_ld['Size']) / block_size)
+
+ plugin_data = "%s:%s:%s" % (ctrl_num, array_num, ld_num)
+
+ # HP SmartArray does not allow disabling volume.
+ return Volume(
+ vpd83, vol_name, vpd83, block_size, num_of_blocks,
+ Volume.ADMIN_STATE_ENABLED, sys_id, pool_id, plugin_data)
+
+ @_handle_errors
+ def volumes(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_vols = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ ctrl_num = ctrl_data['Slot']
+ sys_id = ctrl_data['Serial Number']
+ for key_name in ctrl_data.keys():
+ if not key_name.startswith("Array:"):
+ continue
+ pool_id = _pool_id_of(sys_id, key_name)
+ array_num = key_name[len('Array: '):]
+ for array_key_name in ctrl_data[key_name].keys():
+ if not array_key_name.startswith("Logical Drive"):
+ continue
+ lsm_vols.append(
+ SmartArray._hp_ld_to_lsm_vol(
+ ctrl_data[key_name][array_key_name],
+ pool_id, sys_id, ctrl_num, array_num,
+ array_key_name))
+
+ return search_property(lsm_vols, search_key, search_value)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
index 67734b0..92e27eb 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -35,6 +35,16 @@ def cmd_exec(cmds):
return str_stdout


+def file_read(file_path):
+ """
+ Read file and return string of file content.
+ """
+ fd = open(file_path, 'r')
+ content = fd.read()
+ fd.close()
+ return content
+
+
class ExecError(Exception):
def __init__(self, cmd, errno, stdout, stderr, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
--
1.8.3.1
Gris Ge
2015-03-26 12:59:30 UTC
Permalink
* Treating HP SmartArray physical disk as LSM disk.

* Depending on output of command "hpssacli ctrl all show config detail"

* Only support Disk.STATUS_FREE, Disk.STATUS_OK and Disk.STATUS_OTHER.
# Seeking server to investigate on spare disk and faulty disks.

* Including physical disk location in Disk.name.

* Set Capabilities.DISKS.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index eb5f83b..e65b983 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,7 +21,7 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property, Volume)
+ System, Pool, size_human_2_size_bytes, search_property, Volume, Disk)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read

@@ -164,6 +164,31 @@ def _pool_id_of(sys_id, array_name):
return "%s:%s" % (sys_id, array_name.replace(' ', ''))


+def _disk_type_of(hp_disk):
+ disk_interface = hp_disk['Interface Type']
+ if disk_interface == 'SATA':
+ return Disk.TYPE_SATA
+ elif disk_interface == 'Solid State SATA':
+ return Disk.TYPE_SSD
+ elif disk_interface == 'SAS':
+ return Disk.TYPE_SAS
+
+ return Disk.TYPE_UNKNOWN
+
+
+def _disk_status_of(hp_disk, flag_free):
+ # TODO(Gris Ge): Need more document or test for non-OK disks.
+ if hp_disk['Status'] == 'OK':
+ disk_status = Disk.STATUS_OK
+ else:
+ disk_status = Disk.STATUS_OTHER
+
+ if flag_free:
+ disk_status |= Disk.STATUS_FREE
+
+ return disk_status
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -230,6 +255,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
"System not found")
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
+ cap.set(Capabilities.DISKS)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -377,3 +403,55 @@ def volumes(self, search_key=None, search_value=None,
array_key_name))

return search_property(lsm_vols, search_key, search_value)
+
+ @staticmethod
+ def _hp_disk_to_lsm_disk(hp_disk, sys_id, ctrl_num, key_name,
+ flag_free=False):
+ disk_id = hp_disk['Serial Number']
+ disk_num = key_name[len("physicaldrive "):]
+ disk_name = "%s %s" % (hp_disk['Model'], disk_num)
+ disk_type = _disk_type_of(hp_disk)
+ blk_size = int(hp_disk['Native Block Size'])
+ blk_count = int(_hp_size_to_lsm(hp_disk['Size']) / blk_size)
+ status = _disk_status_of(hp_disk, flag_free)
+ plugin_data = "%s:%s" % (ctrl_num, disk_num)
+
+ return Disk(
+ disk_id, disk_name, disk_type, blk_size, blk_count,
+ status, sys_id, plugin_data)
+
+ @_handle_errors
+ def disks(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ # TODO(Gris Ge): Need real test on spare disk and free disk.
+ # The free disks is purely base on HP document.
+ rc_lsm_disks = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ if key_name == 'unassigned':
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ return search_property(rc_lsm_disks, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-26 12:59:31 UTC
Permalink
* Depending on output of command 'hpssacli ctrl slot=X show config detail'

* The hpssacli provide strip size and stripe size which is min_io_size and
opt_io_size. They are identical to values of Linux sysfs equivalent
entries.

* Set Capabilities.VOLUME_RAID_INFO.

Changes in V2:

* Initialize these variables in volume_raid_info():
strip_size, stripe_size, raid_type

* Add an extra check to raise PLUGIN_BUG just in case we got LD but no PD
entry when checking raid disk members in volume_raid_info().

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

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index e65b983..60d87cc 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -189,6 +189,28 @@ def _disk_status_of(hp_disk, flag_free):
return disk_status


+def _hp_raid_type_to_lsm(hp_ld):
+ """
+ Based on this property:
+ Fault Tolerance: 0/1/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ if hp_raid_level == '0':
+ return Volume.RAID_TYPE_RAID0
+ elif hp_raid_level == '1':
+ # TODO(Gris Ge): Investigate whether HP has 4 disks RAID 1.
+ # In LSM, that's RAID10.
+ return Volume.RAID_TYPE_RAID1
+ elif hp_raid_level == '5':
+ return Volume.RAID_TYPE_RAID5
+ elif hp_raid_level == '6':
+ return Volume.RAID_TYPE_RAID6
+ elif hp_raid_level == '1+0':
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -256,6 +278,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUME_RAID_INFO)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -455,3 +478,51 @@ def disks(self, search_key=None, search_value=None,
flag_free=False))

return search_property(rc_lsm_disks, search_key, search_value)
+
+ @_handle_errors
+ def volume_raid_info(self, volume, flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl slot=0 show config detail
+ """
+ if not volume.plugin_data:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+ ctrl_data = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"]
+ ).values()[0]
+
+ disk_count = 0
+ strip_size = Volume.STRIP_SIZE_UNKNOWN
+ stripe_size = Volume.OPT_IO_SIZE_UNKNOWN
+ raid_type = Volume.RAID_TYPE_UNKNOWN
+ for key_name in ctrl_data.keys():
+ if key_name != "Array: %s" % array_num:
+ continue
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name == "Logical Drive: %s" % ld_num:
+ hp_ld = ctrl_data[key_name][array_key_name]
+ raid_type = _hp_raid_type_to_lsm(hp_ld)
+ strip_size = _hp_size_to_lsm(hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(hp_ld['Full Stripe Size'])
+ elif array_key_name.startswith("physicaldrive"):
+ hp_disk = ctrl_data[key_name][array_key_name]
+ if hp_disk['Drive Type'] == 'Data Drive':
+ disk_count += 1
+
+ if disk_count == 0:
+ if strip_size == Volume.STRIP_SIZE_UNKNOWN:
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "volume_raid_info(): Got logical drive %s entry, " %
+ ld_num + "but no physicaldrive entry: %s" %
+ ctrl_data.items())
+
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
--
1.8.3.1
Gris Ge
2015-03-31 08:40:49 UTC
Permalink
* The new hpsa plugin is based on HP binary tool -- hpssacli which
could be downloaded from:
http://downloads.linux.hp.com/SDR/project/spp/

* This patch set introduced these methods support to hpsa plugin:
* lsm.Client.systems()
* lsm.Client.pools()
* lsm.Client.volumes()
* lsm.Client.disks()
# With Disk.STATUS_FREE support also.
* lsm.Client.volume_raid_info()

* This patch set only coded and tested on 2 servers(Tony's and mine), a
automatic tests is running in our internal lab to test this plugin on more
HP SmartArray cards, will provide subsequent patch if found any bug.

Changes in V2:

* Patch 2/6:
* Fix missing '()' in _parse_hpssacli_output().
* Handle 'No controllers detected' error.

* Patch 6/6:
* Fix uninitialized variables in volume_raid_info().
* Add an extra check to raise PLUGIN_BUG when LD found with no PD found.

* Please be informed, on RHEL 7.1, you need to manually run 'modprobe sg'
command before using this plugin.

Changes in V3:

* Moved 'utils.py' to patch 2/6.

* Patch 2/6:

Fixed a 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:'

* Patch 4/6: Changed file_read() to use python fileIO(open,read,close).

* Tested on HP SmartArray P420i with plugin_test.py(only got expected failure
for test_timeout).

Changes in V4:
* Add new patch 7/7: Fix bug of parsing free disks.

Gris Ge (7):
New plugin: HP SmartArray Plugin.
HP SmartArray Plugin: Add systems() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.volumes() support.
HP SmartArray Plugin: Add lsm.Client.disks() support.
HP SmartArray Plugin: Add lsm.Client.volume_raid_info() support.
HP SmartArray Plugin: Fix bug of parsing free disks.

config/Makefile.am | 5 +
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 +
doc/man/Makefile.am | 4 +
doc/man/hpsa_lsmplugin.1.in | 50 ++++
packaging/libstoragemgmt.spec.in | 39 +++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 572 +++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 58 ++++
12 files changed, 790 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
--
1.8.3.1
Gris Ge
2015-03-31 08:40:51 UTC
Permalink
* 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 <***@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 <***@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 <***@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
Gris Ge
2015-03-31 08:40:50 UTC
Permalink
* New empty(no real function) plugin: HP SmartArray with URI 'hpsa://' and
no password required.

* The hpsa plugin will use HP binary tool -- hpssacli to control localhost
HP SmartArray cards. Hence root privilege required.

* This patch contains:
1. Autotools update for new plugin.
2. RPM SPEC file update for new plugin package:
libstoragemgmt-hpsa-plugin (Fedora/RHEL)
libstoragemgmt1-hpsa-plugin (OpenSuSE)
3. New manpage: hpsa_lsmplugin.1
4. The lsm plugin configuration file for root privilege requirement.

Changes in V3(no changes in V2):

* Move the 'utils.py' files to next commit along with its rpm spec and
automake file update.

Signed-off-by: Gris Ge <***@redhat.com>
---
config/Makefile.am | 5 ++++
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 +++++++++++
doc/man/Makefile.am | 4 ++++
doc/man/hpsa_lsmplugin.1.in | 50 ++++++++++++++++++++++++++++++++++++++
packaging/libstoragemgmt.spec.in | 38 +++++++++++++++++++++++++++++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +++++++
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 52 ++++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 ++++++++++++++++++++++++++++
11 files changed, 211 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin

diff --git a/config/Makefile.am b/config/Makefile.am
index 70ab97b..39fb224 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -11,3 +11,8 @@ if WITH_MEGARAID
pluginconf_DATA += pluginconf.d/megaraid.conf
EXTRA_DIST += pluginconf.d/megaraid.conf
endif
+
+if WITH_HPSA
+pluginconf_DATA += pluginconf.d/hpsa.conf
+EXTRA_DIST += pluginconf.d/hpsa.conf
+endif
diff --git a/config/pluginconf.d/hpsa.conf b/config/pluginconf.d/hpsa.conf
new file mode 100644
index 0000000..35b52d4
--- /dev/null
+++ b/config/pluginconf.d/hpsa.conf
@@ -0,0 +1 @@
+require-root-privilege = true;
diff --git a/configure.ac b/configure.ac
index db657bb..9315f35 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,6 +193,18 @@ AC_ARG_WITH([megaraid],
AM_CONDITIONAL([WITH_MEGARAID], [test "x$with_megaraid" = "xyes"])

dnl ==========================================================================
+dnl Add option '--without-hpsa' to exclude hpsa plugin.
+dnl ==========================================================================
+
+AC_ARG_WITH([hpsa],
+ [AS_HELP_STRING([--without-hpsa],
+ [Do not build the HP SmartArray plugin])],
+ [],
+ [with_hpsa=yes])
+
+AM_CONDITIONAL([WITH_HPSA], [test "x$with_hpsa" = "xyes"])
+
+dnl ==========================================================================
dnl Check for libconfig as it is needed for lsmd daemon
dnl ==========================================================================
PKG_CHECK_MODULES(
@@ -221,6 +233,7 @@ AC_OUTPUT(libstoragemgmt.pc \
plugin/Makefile \
plugin/simc/Makefile \
plugin/megaraid/Makefile \
+ plugin/hpsa/Makefile \
daemon/Makefile \
config/Makefile \
doc/Makefile \
@@ -229,6 +242,7 @@ AC_OUTPUT(libstoragemgmt.pc \
doc/doxygen.conf \
doc/man/lsmd.conf.5 \
doc/man/megaraid_lsmplugin.1 \
+ doc/man/hpsa_lsmplugin.1 \
tools/Makefile \
tools/udev/Makefile \
tools/lsmcli/Makefile \
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
index ccbcb38..19b536c 100644
--- a/doc/man/Makefile.am
+++ b/doc/man/Makefile.am
@@ -4,4 +4,8 @@ if WITH_MEGARAID
notrans_dist_man1_MANS += megaraid_lsmplugin.1
endif

+if WITH_HPSA
+notrans_dist_man1_MANS += hpsa_lsmplugin.1
+endif
+
notrans_dist_man5_MANS = lsmd.conf.5.in
diff --git a/doc/man/hpsa_lsmplugin.1.in b/doc/man/hpsa_lsmplugin.1.in
new file mode 100644
index 0000000..79ff565
--- /dev/null
+++ b/doc/man/hpsa_lsmplugin.1.in
@@ -0,0 +1,50 @@
+.TH hpsa_lsmplugin "1" "March 2015" "hpsa_lsmplugin @VERSION@" "libStorageMgmt"
+.SH NAME
+hpsa_lsmplugin -- LibstorageMgmt HP SmartArray plugin
+
+.SH DESCRIPTION
+LibstorageMgmt hpsa plugin allows user to manage HP SmartArray via vendor
+tool \fBhpssacli\fR[1].
+The 'hpsa_lsmplugin' executable file is for libStorageMgmt
+daemon to execute when client user specifies hpsa plugin in the URI.
+
+.SH URI
+To use this plugin, users should set their URI to this format:
+.nf
+
+ \fBhpsa://\fR
+ or
+ \fBhpsa://?hpssacli=<path_of_hpssacli>\fR
+
+.fi
+
+.TP hpssacli
+The 'hpssacli' URI parameter is used to specified the path of hpssacli tool.
+By default, this plugin will try these paths used by hpssacli rpm:
+\fB/usr/sbin/hpssacli\fR and \fB/opt/hp/hpssacli/bld/hpssacli\fR.
+
+.SH ROOT PRIVILEGE
+This plugin requires both \fBlsmd\fR daemon and API client running as root
+user. Please check manpage \fIlsmd.conf (5)\fR for detail.
+
+.SH SUPPORTED HARDWARES
+Please refer to HP website for hardware support status of hpssacli.
+Detailed support status can be queried via:
+
+ * \fBlsm.Client.capabilities()\fR (Python API)
+ * \fBlsm_capabilities()\fR (C API)
+ * \fBlsmcli capabilities\fR (lsmcli command line).
+
+.SH FIREWALL RULES
+This plugin only execute \fBhpssacli\fR on localhost. No network connection
+required.
+
+.SH SEE ALSO
+\fIlsmcli\fR(1), \fIlsmd\fR(1), [1]http://downloads.linux.hp.com/SDR/project/spp/
+
+.SH BUGS
+Please report bugs to
+\fI<libstoragemgmt-***@lists.sourceforge.net>\fR
+
+.SH AUTHOR
+Gris Ge \fI<***@redhat.com>\fR
diff --git a/packaging/libstoragemgmt.spec.in b/packaging/libstoragemgmt.spec.in
index 1ca0eea..9e24faa 100644
--- a/packaging/libstoragemgmt.spec.in
+++ b/packaging/libstoragemgmt.spec.in
@@ -1,5 +1,6 @@
%bcond_with rest_api
%bcond_without megaraid
+%bcond_without hpsa

# Use one-line macro for OBS workaround:
# https://bugzilla.novell.com/show_bug.cgi?id=864323
@@ -9,6 +10,9 @@
%{?_with_megaraid: %global with_megaraid 1 }
%{?_without_megaraid: %global with_megaraid 0 }

+%{?_with_hpsa: %global with_hpsa 1 }
+%{?_without_hpsa: %global with_hpsa 0 }
+
%define libsoname libstoragemgmt

%if 0%{?suse_version} || 0%{?fedora} >= 15 || 0%{?rhel} >= 7
@@ -224,6 +228,17 @@ The %{libstoragemgmt}-megaraid-plugin package contains the plugin for LSI
MegaRAID storage management via storcli.
%endif

+%if 0%{?with_hpsa}
+%package -n %{libstoragemgmt}-hpsa-plugin
+Summary: Files for HP SmartArray support for %{libstoragemgmt}
+Group: System Environment/Libraries
+Requires: %{libstoragemgmt}%{?_isa} = %{version}-%{release}
+BuildArch: noarch
+
+%description -n %{libstoragemgmt}-hpsa-plugin
+The %{libstoragemgmt}-hpsa-plugin package contains the plugin for HP
+SmartArray storage management via hpssacli.
+%endif

%prep
%setup -q
@@ -238,6 +253,9 @@ MegaRAID storage management via storcli.
%if 0%{?with_megaraid} != 1
--without-megaraid \
%endif
+%if 0%{?with_hpsa} != 1
+ --without-hpsa \
+%endif
--disable-static

V=1 make %{?_smp_mflags}
@@ -450,6 +468,15 @@ if [ $1 -eq 0 ]; then
fi
%endif

+%if 0%{?with_hpsa}
+%postun -n %{libstoragemgmt}-hpsa-plugin
+if [ $1 -eq 0 ]; then
+ # Remove
+ /usr/bin/systemctl try-restart libstoragemgmt.service \
+ >/dev/null 2>&1 || :
+fi
+%endif
+
%files -n %{libstoragemgmt}
%defattr(-,root,root,-)
%doc README COPYING.LIB
@@ -557,6 +584,17 @@ fi
%{_mandir}/man1/megaraid_lsmplugin.1*
%endif

+%if 0%{?with_hpsa}
+%files -n %{libstoragemgmt}-hpsa-plugin
+%defattr(-,root,root,-)
+%dir %{python_sitelib}/lsm/plugin/hpsa
+%{python_sitelib}/lsm/plugin/hpsa/__init__.*
+%{python_sitelib}/lsm/plugin/hpsa/hpsa.*
+%{_bindir}/hpsa_lsmplugin
+%{_sysconfdir}/lsm/pluginconf.d/hpsa.conf
+%{_mandir}/man1/hpsa_lsmplugin.1*
+%endif
+
%files udev
%defattr(-,root,root,-)
%{udev_dir}/udev/scan-scsi-target
diff --git a/plugin/Makefile.am b/plugin/Makefile.am
index f9146a8..3c35fa4 100644
--- a/plugin/Makefile.am
+++ b/plugin/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS=simc megaraid
+SUBDIRS=simc megaraid hpsa

plugindir = $(pythondir)/lsm/plugin

diff --git a/plugin/hpsa/Makefile.am b/plugin/hpsa/Makefile.am
new file mode 100644
index 0000000..46344c0
--- /dev/null
+++ b/plugin/hpsa/Makefile.am
@@ -0,0 +1,8 @@
+if WITH_HPSA
+plugindir = $(pythondir)/lsm/plugin
+hpsadir = $(plugindir)/hpsa
+
+hpsa_PYTHON = __init__.py hpsa.py
+
+dist_bin_SCRIPTS= hpsa_lsmplugin
+endif
diff --git a/plugin/hpsa/__init__.py b/plugin/hpsa/__init__.py
new file mode 100644
index 0000000..61332b4
--- /dev/null
+++ b/plugin/hpsa/__init__.py
@@ -0,0 +1 @@
+from lsm.plugin.hpsa.hpsa import SmartArray
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
new file mode 100644
index 0000000..46a093e
--- /dev/null
+++ b/plugin/hpsa/hpsa.py
@@ -0,0 +1,52 @@
+# 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 <***@redhat.com>
+
+from lsm import (
+ IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber)
+
+
+class SmartArray(IPlugin):
+ def plugin_register(self, uri, password, timeout, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_unregister(self, flags=Client.FLAG_RSVD):
+ pass
+
+ def job_status(self, job_id, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def job_free(self, job_id, flags=Client.FLAG_RSVD):
+ pass
+
+ def plugin_info(self, flags=Client.FLAG_RSVD):
+ return "HP SmartArray Plugin", VERSION
+
+ def time_out_set(self, ms, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ def time_out_get(self, flags=Client.FLAG_RSVD):
+ raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+
+ 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 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/hpsa_lsmplugin b/plugin/hpsa/hpsa_lsmplugin
new file mode 100755
index 0000000..07230f5
--- /dev/null
+++ b/plugin/hpsa/hpsa_lsmplugin
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+# 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: tasleson
+# Gris Ge <***@redhat.com>
+
+import sys
+import syslog
+import traceback
+
+try:
+ from lsm import PluginRunner
+ from lsm.plugin.hpsa import SmartArray
+
+ if __name__ == '__main__':
+ PluginRunner(SmartArray, sys.argv).run()
+except Exception:
+ #This should be quite rare, but when it does happen this is pretty
+ #key in understanding what happened, especially when it happens when
+ #running from the daemon.
+ msg = str(traceback.format_exc())
+ syslog.syslog(syslog.LOG_ERR, msg)
+ sys.stderr.write(msg)
+ sys.exit(1)
--
1.8.3.1
Gris Ge
2015-03-31 08:40:52 UTC
Permalink
* Treating HP SmartArray 'Array' term as LSM Pool.

* Depending on output of 'hpssacli ctrl all show config detail'.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 74 insertions(+), 2 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 4e4b5ac..19f50dc 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -17,10 +17,11 @@

import os
import errno
+import re

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System)
+ System, Pool, size_human_2_size_bytes, search_property)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError

@@ -131,6 +132,38 @@ def _parse_hpssacli_output(output):
return data


+def _hp_size_to_lsm(hp_size):
+ """
+ HP Using 'TB, GB, MB, KB' and etc, for LSM, they are 'TiB' and etc.
+ Return int of block bytes
+ """
+ re_regex = re.compile("^([0-9.]+) +([EPTGMK])B$")
+ re_match = re_regex.match(hp_size)
+ if re_match:
+ return size_human_2_size_bytes(
+ "%s%siB" % (re_match.group(1), re_match.group(2)))
+
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_hp_size_to_lsm(): Got unexpected HP size string %s" %
+ hp_size)
+
+
+def _pool_status_of(hp_array):
+ """
+ Return (status, status_info)
+ """
+ if hp_array['Status'] == 'OK':
+ return Pool.STATUS_OK, ''
+ else:
+ # TODO(Gris Ge): Try degrade a RAID or fail a RAID.
+ return Pool.STATUS_OTHER, hp_array['Status']
+
+
+def _pool_id_of(sys_id, array_name):
+ return "%s:%s" % (sys_id, array_name.replace(' ', ''))
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -238,7 +271,46 @@ def systems(self, flags=0):

return rc_lsm_syss

+ @staticmethod
+ def _hp_array_to_lsm_pool(hp_array, array_name, sys_id, ctrl_num):
+ pool_id = _pool_id_of(sys_id, array_name)
+ name = array_name
+ elem_type = Pool.ELEMENT_TYPE_VOLUME | Pool.ELEMENT_TYPE_VOLUME_FULL
+ unsupported_actions = 0
+ # TODO(Gris Ge): HP does not provide a precise number of bytes.
+ free_space = _hp_size_to_lsm(hp_array['Unused Space'])
+ total_space = free_space
+ for key_name in hp_array.keys():
+ if key_name.startswith('Logical Drive'):
+ total_space += _hp_size_to_lsm(hp_array[key_name]['Size'])
+
+ (status, status_info) = _pool_status_of(hp_array)
+
+ plugin_data = "%s:%s" % (
+ ctrl_num, array_name[len("Array: "):])
+
+ return Pool(
+ pool_id, name, elem_type, unsupported_actions,
+ total_space, free_space, status, status_info,
+ sys_id, plugin_data)
+
@_handle_errors
def pools(self, search_key=None, search_value=None,
flags=Client.FLAG_RSVD):
- raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_pools = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ lsm_pools.append(
+ SmartArray._hp_array_to_lsm_pool(
+ ctrl_data[key_name], key_name, sys_id, ctrl_num))
+
+ return search_property(lsm_pools, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-31 08:40:53 UTC
Permalink
* Treating HP SmartArray logical drive as LSM volume.

* Depend on output of command "hpssacli ctrl all show config detail".

* New method of utils.py:
* file_read()
# Simply use 'cat' command to do file read which provide better
# error handling using ExecError.

* The block size and block count is read from linux sysfs as hpssacli
provide the sdX name for each logical drive.
Faillback to 512 and calculated from other output entry.

* Include sdX name if found in Volume.name.

* Set Capabilities.VOLUMES.

Changes in V3(no changes in v2):

* Changed file_read() to use python fileIO(open,read,close).

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---
plugin/hpsa/utils.py | 10 ++++++++
2 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 19f50dc..eb5f83b 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,9 +21,9 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property)
+ System, Pool, size_human_2_size_bytes, search_property, Volume)

-from lsm.plugin.hpsa.utils import cmd_exec, ExecError
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read


def _handle_errors(method):
@@ -223,7 +223,14 @@ def time_out_get(self, flags=Client.FLAG_RSVD):

@_handle_errors
def capabilities(self, system, flags=Client.FLAG_RSVD):
- return Capabilities()
+ cur_lsm_syss = self.systems()
+ if system.id not in list(s.id for s in cur_lsm_syss):
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_SYSTEM,
+ "System not found")
+ cap = Capabilities()
+ cap.set(Capabilities.VOLUMES)
+ return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
"""
@@ -314,3 +321,59 @@ def pools(self, search_key=None, search_value=None,
ctrl_data[key_name], key_name, sys_id, ctrl_num))

return search_property(lsm_pools, search_key, search_value)
+
+ @staticmethod
+ def _hp_ld_to_lsm_vol(hp_ld, pool_id, sys_id, ctrl_num, array_num,
+ hp_ld_name):
+ ld_num = hp_ld_name[len("Logical Drive: "):]
+ vpd83 = hp_ld['Unique Identifier'].lower()
+ # No document or command output indicate block size
+ # of volume. So we try to read from linux kernel, if failed
+ # try 512 and roughly calculate the sector count.
+ regex_match = re.compile("/dev/(sd[a-z]+)").search(hp_ld['Disk Name'])
+ vol_name = hp_ld_name
+ if regex_match:
+ sd_name = regex_match.group(1)
+ block_size = int(file_read(
+ "/sys/block/%s/queue/logical_block_size" % sd_name))
+ num_of_blocks = int(file_read("/sys/block/%s/size" % sd_name))
+ vol_name += ": /dev/%s" % sd_name
+ else:
+ block_size = 512
+ num_of_blocks = int(_hp_size_to_lsm(hp_ld['Size']) / block_size)
+
+ plugin_data = "%s:%s:%s" % (ctrl_num, array_num, ld_num)
+
+ # HP SmartArray does not allow disabling volume.
+ return Volume(
+ vpd83, vol_name, vpd83, block_size, num_of_blocks,
+ Volume.ADMIN_STATE_ENABLED, sys_id, pool_id, plugin_data)
+
+ @_handle_errors
+ def volumes(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_vols = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ ctrl_num = ctrl_data['Slot']
+ sys_id = ctrl_data['Serial Number']
+ for key_name in ctrl_data.keys():
+ if not key_name.startswith("Array:"):
+ continue
+ pool_id = _pool_id_of(sys_id, key_name)
+ array_num = key_name[len('Array: '):]
+ for array_key_name in ctrl_data[key_name].keys():
+ if not array_key_name.startswith("Logical Drive"):
+ continue
+ lsm_vols.append(
+ SmartArray._hp_ld_to_lsm_vol(
+ ctrl_data[key_name][array_key_name],
+ pool_id, sys_id, ctrl_num, array_num,
+ array_key_name))
+
+ return search_property(lsm_vols, search_key, search_value)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
index 67734b0..92e27eb 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -35,6 +35,16 @@ def cmd_exec(cmds):
return str_stdout


+def file_read(file_path):
+ """
+ Read file and return string of file content.
+ """
+ fd = open(file_path, 'r')
+ content = fd.read()
+ fd.close()
+ return content
+
+
class ExecError(Exception):
def __init__(self, cmd, errno, stdout, stderr, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
--
1.8.3.1
Gris Ge
2015-03-31 08:40:54 UTC
Permalink
* Treating HP SmartArray physical disk as LSM disk.

* Depending on output of command "hpssacli ctrl all show config detail"

* Only support Disk.STATUS_FREE, Disk.STATUS_OK and Disk.STATUS_OTHER.
# Seeking server to investigate on spare disk and faulty disks.

* Including physical disk location in Disk.name.

* Set Capabilities.DISKS.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index eb5f83b..e65b983 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,7 +21,7 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property, Volume)
+ System, Pool, size_human_2_size_bytes, search_property, Volume, Disk)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read

@@ -164,6 +164,31 @@ def _pool_id_of(sys_id, array_name):
return "%s:%s" % (sys_id, array_name.replace(' ', ''))


+def _disk_type_of(hp_disk):
+ disk_interface = hp_disk['Interface Type']
+ if disk_interface == 'SATA':
+ return Disk.TYPE_SATA
+ elif disk_interface == 'Solid State SATA':
+ return Disk.TYPE_SSD
+ elif disk_interface == 'SAS':
+ return Disk.TYPE_SAS
+
+ return Disk.TYPE_UNKNOWN
+
+
+def _disk_status_of(hp_disk, flag_free):
+ # TODO(Gris Ge): Need more document or test for non-OK disks.
+ if hp_disk['Status'] == 'OK':
+ disk_status = Disk.STATUS_OK
+ else:
+ disk_status = Disk.STATUS_OTHER
+
+ if flag_free:
+ disk_status |= Disk.STATUS_FREE
+
+ return disk_status
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -230,6 +255,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
"System not found")
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
+ cap.set(Capabilities.DISKS)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -377,3 +403,55 @@ def volumes(self, search_key=None, search_value=None,
array_key_name))

return search_property(lsm_vols, search_key, search_value)
+
+ @staticmethod
+ def _hp_disk_to_lsm_disk(hp_disk, sys_id, ctrl_num, key_name,
+ flag_free=False):
+ disk_id = hp_disk['Serial Number']
+ disk_num = key_name[len("physicaldrive "):]
+ disk_name = "%s %s" % (hp_disk['Model'], disk_num)
+ disk_type = _disk_type_of(hp_disk)
+ blk_size = int(hp_disk['Native Block Size'])
+ blk_count = int(_hp_size_to_lsm(hp_disk['Size']) / blk_size)
+ status = _disk_status_of(hp_disk, flag_free)
+ plugin_data = "%s:%s" % (ctrl_num, disk_num)
+
+ return Disk(
+ disk_id, disk_name, disk_type, blk_size, blk_count,
+ status, sys_id, plugin_data)
+
+ @_handle_errors
+ def disks(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ # TODO(Gris Ge): Need real test on spare disk and free disk.
+ # The free disks is purely base on HP document.
+ rc_lsm_disks = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ if key_name == 'unassigned':
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ return search_property(rc_lsm_disks, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-31 08:40:55 UTC
Permalink
* Depending on output of command 'hpssacli ctrl slot=X show config detail'

* The hpssacli provide strip size and stripe size which is min_io_size and
opt_io_size. They are identical to values of Linux sysfs equivalent
entries.

* Set Capabilities.VOLUME_RAID_INFO.

Changes in V2:

* Initialize these variables in volume_raid_info():
strip_size, stripe_size, raid_type

* Add an extra check to raise PLUGIN_BUG just in case we got LD but no PD
entry when checking raid disk members in volume_raid_info().

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

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index e65b983..60d87cc 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -189,6 +189,28 @@ def _disk_status_of(hp_disk, flag_free):
return disk_status


+def _hp_raid_type_to_lsm(hp_ld):
+ """
+ Based on this property:
+ Fault Tolerance: 0/1/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ if hp_raid_level == '0':
+ return Volume.RAID_TYPE_RAID0
+ elif hp_raid_level == '1':
+ # TODO(Gris Ge): Investigate whether HP has 4 disks RAID 1.
+ # In LSM, that's RAID10.
+ return Volume.RAID_TYPE_RAID1
+ elif hp_raid_level == '5':
+ return Volume.RAID_TYPE_RAID5
+ elif hp_raid_level == '6':
+ return Volume.RAID_TYPE_RAID6
+ elif hp_raid_level == '1+0':
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -256,6 +278,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUME_RAID_INFO)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -455,3 +478,51 @@ def disks(self, search_key=None, search_value=None,
flag_free=False))

return search_property(rc_lsm_disks, search_key, search_value)
+
+ @_handle_errors
+ def volume_raid_info(self, volume, flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl slot=0 show config detail
+ """
+ if not volume.plugin_data:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+ ctrl_data = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"]
+ ).values()[0]
+
+ disk_count = 0
+ strip_size = Volume.STRIP_SIZE_UNKNOWN
+ stripe_size = Volume.OPT_IO_SIZE_UNKNOWN
+ raid_type = Volume.RAID_TYPE_UNKNOWN
+ for key_name in ctrl_data.keys():
+ if key_name != "Array: %s" % array_num:
+ continue
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name == "Logical Drive: %s" % ld_num:
+ hp_ld = ctrl_data[key_name][array_key_name]
+ raid_type = _hp_raid_type_to_lsm(hp_ld)
+ strip_size = _hp_size_to_lsm(hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(hp_ld['Full Stripe Size'])
+ elif array_key_name.startswith("physicaldrive"):
+ hp_disk = ctrl_data[key_name][array_key_name]
+ if hp_disk['Drive Type'] == 'Data Drive':
+ disk_count += 1
+
+ if disk_count == 0:
+ if strip_size == Volume.STRIP_SIZE_UNKNOWN:
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "volume_raid_info(): Got logical drive %s entry, " %
+ ld_num + "but no physicaldrive entry: %s" %
+ ctrl_data.items())
+
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
--
1.8.3.1
Gris Ge
2015-03-31 08:40:56 UTC
Permalink
* The _parse_hpssacli_output() method will fail if got free disks detected.
This fix is removing list used in _parse_hpssacli_output(), every line
will be stored as an dictionary key.
If current line is not 'key: value' format, the value will be set as None.
Also skipped the incorrect indented 'Physical Drives' line, in the seek
of avoiding duplication dictionary key.

* Also fix the incorrect 'flag_free' argument used for
SmartArray._hp_disk_to_lsm_disk() when facing free disks.

* Tested on HP P410i with one free disk, hence updated the TODO line about
free disks.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 72 ++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 58 insertions(+), 14 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 60d87cc..743cbcc 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -75,9 +75,20 @@ def _sys_status_of(hp_ctrl_status):
def _parse_hpssacli_output(output):
"""
Got a output string of hpssacli to dictionary(nested).
+ Skipped these line:
+ 1. Starts with 'Note:'
+ This is just a message right after controller. We don't neet it
+ yet.
+ 2. The 'Physical Drives' line.
+ It should indented after 'Internal Drive Cage' like.
+ If not ignored, we might got duplication line error.
+ After ignored, it's phsycial disks will directly stored as
+ key of 'Internal Drive Cage' dictionary.
"""
output_lines = [
- l for l in output.split("\n") if l and not l.startswith('Note:')]
+ l for l in output.split("\n")
+ if l and not l.startswith('Note:') and
+ not l.strip() == 'Physical Drives']

data = {}

@@ -108,24 +119,19 @@ def _parse_hpssacli_output(output):

if nxt_indent_count > cur_indent_count:
nxt_line_splitted = nxt_line.split(": ")
- if len(nxt_line_splitted) == 1:
- new_data = []
- else:
- new_data = {}
+ 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):
+ else:
raise LsmError(
ErrorNumber.PLUGIN_BUG,
- "_parse_hpssacli_output(): Unexpected line '%s%s\n'" %
- (cur_line, nxt_line))
+ "_parse_hpssacli_output(): Found duplicate line %s" %
+ cur_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())
+ cur_data_pointer[cur_line.lstrip()] = None
else:
cur_data_pointer[cur_line_splitted[0].lstrip()] = \
": ".join(cur_line_splitted[1:]).strip()
@@ -279,6 +285,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.DISKS)
cap.set(Capabilities.VOLUME_RAID_INFO)
+ cap.set(Capabilities.POOL_RAID_INFO)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -450,8 +457,7 @@ def disks(self, search_key=None, search_value=None,
Depend on command:
hpssacli ctrl all show config detail
"""
- # TODO(Gris Ge): Need real test on spare disk and free disk.
- # The free disks is purely base on HP document.
+ # TODO(Gris Ge): Need real test on spare disk.
rc_lsm_disks = []
ctrl_all_conf = self._sacli_exec(
["ctrl", "all", "show", "config", "detail"])
@@ -475,7 +481,7 @@ def disks(self, search_key=None, search_value=None,
SmartArray._hp_disk_to_lsm_disk(
ctrl_data[key_name][array_key_name],
sys_id, ctrl_num, array_key_name,
- flag_free=False))
+ flag_free=True))

return search_property(rc_lsm_disks, search_key, search_value)

@@ -526,3 +532,41 @@ def volume_raid_info(self, volume, flags=Client.FLAG_RSVD):
"Volume not found")

return [raid_type, strip_size, disk_count, strip_size, stripe_size]
+
+ @_handle_errors
+ def pool_raid_info(self, pool, flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl slot=0 show config detail
+ """
+ if not pool.plugin_data:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num) = pool.plugin_data.split(":")
+ ctrl_data = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"]
+ ).values()[0]
+
+ disk_ids = []
+ raid_type = Volume.RAID_TYPE_UNKNOWN
+ for key_name in ctrl_data.keys():
+ if key_name == "Array: %s" % array_num:
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("Logical Drive: ") and \
+ raid_type == Volume.RAID_TYPE_UNKNOWN:
+ raid_type = _hp_raid_type_to_lsm(
+ ctrl_data[key_name][array_key_name])
+ elif array_key_name.startswith("physicaldrive"):
+ hp_disk = ctrl_data[key_name][array_key_name]
+ if hp_disk['Drive Type'] == 'Data Drive':
+ disk_ids.append(hp_disk['Serial Number'])
+ break
+
+ if len(disk_ids) == 0:
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_POOL,
+ "Pool not found")
+
+ return raid_type, Pool.MEMBER_TYPE_DISK, disk_ids
--
1.8.3.1
Gris Ge
2015-03-31 08:52:39 UTC
Permalink
Post by Gris Ge
* The _parse_hpssacli_output() method will fail if got free disks detected.
This fix is removing list used in _parse_hpssacli_output(), every line
will be stored as an dictionary key.
If current line is not 'key: value' format, the value will be set as None.
Also skipped the incorrect indented 'Physical Drives' line, in the seek
of avoiding duplication dictionary key.
* Also fix the incorrect 'flag_free' argument used for
SmartArray._hp_disk_to_lsm_disk() when facing free disks.
* Tested on HP P410i with one free disk, hence updated the TODO line about
free disks.
This patch is incorrectly included the pool_raid_info() method.
Please check github pull request or use attached one.

Sorry for the mess.
Post by Gris Ge
---
--
Gris Ge
Tony Asleson
2015-04-01 19:53:09 UTC
Permalink
Hi Gris,

I built some rpm's for RHEL7 and installed them on a test box along with
the hpsa cli (hpssacli-2.10-14.0.x86_64).

When I try to list the systems I get:

NOT_FOUND_SYSTEM(208): No HP SmartArray deteceted by hpssacli.

If I...
# setenforce 0

it works.

I see this in the system log:

SELinux is preventing /opt/hp/hpssacli/bld/hpssacli from read access on
the directory .

Have you run into this before? I didn't run into it last time I used
one of the test systems so I'm a little confused why it's being a
problem now. I'm sure the default is enabled...

Otherwise everything seems to check out!

Thanks,
Tony
Post by Gris Ge
* The new hpsa plugin is based on HP binary tool -- hpssacli which
http://downloads.linux.hp.com/SDR/project/spp/
* lsm.Client.systems()
* lsm.Client.pools()
* lsm.Client.volumes()
* lsm.Client.disks()
# With Disk.STATUS_FREE support also.
* lsm.Client.volume_raid_info()
* This patch set only coded and tested on 2 servers(Tony's and mine), a
automatic tests is running in our internal lab to test this plugin on more
HP SmartArray cards, will provide subsequent patch if found any bug.
* Fix missing '()' in _parse_hpssacli_output().
* Handle 'No controllers detected' error.
* Fix uninitialized variables in volume_raid_info().
* Add an extra check to raise PLUGIN_BUG when LD found with no PD found.
* Please be informed, on RHEL 7.1, you need to manually run 'modprobe sg'
command before using this plugin.
* Moved 'utils.py' to patch 2/6.
Fixed a bug in _parse_hpssacli_output() when 'hpssacli' have this kind of
```
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:'
* Patch 4/6: Changed file_read() to use python fileIO(open,read,close).
* Tested on HP SmartArray P420i with plugin_test.py(only got expected failure
for test_timeout).
* Add new patch 7/7: Fix bug of parsing free disks.
New plugin: HP SmartArray Plugin.
HP SmartArray Plugin: Add systems() support.
HP SmartArray Plugin: Add lsm.Client.pools() support.
HP SmartArray Plugin: Add lsm.Client.volumes() support.
HP SmartArray Plugin: Add lsm.Client.disks() support.
HP SmartArray Plugin: Add lsm.Client.volume_raid_info() support.
HP SmartArray Plugin: Fix bug of parsing free disks.
config/Makefile.am | 5 +
config/pluginconf.d/hpsa.conf | 1 +
configure.ac | 14 +
doc/man/Makefile.am | 4 +
doc/man/hpsa_lsmplugin.1.in | 50 ++++
packaging/libstoragemgmt.spec.in | 39 +++
plugin/Makefile.am | 2 +-
plugin/hpsa/Makefile.am | 8 +
plugin/hpsa/__init__.py | 1 +
plugin/hpsa/hpsa.py | 572 +++++++++++++++++++++++++++++++++++++++
plugin/hpsa/hpsa_lsmplugin | 37 +++
plugin/hpsa/utils.py | 58 ++++
12 files changed, 790 insertions(+), 1 deletion(-)
create mode 100644 config/pluginconf.d/hpsa.conf
create mode 100644 doc/man/hpsa_lsmplugin.1.in
create mode 100644 plugin/hpsa/Makefile.am
create mode 100644 plugin/hpsa/__init__.py
create mode 100644 plugin/hpsa/hpsa.py
create mode 100755 plugin/hpsa/hpsa_lsmplugin
create mode 100644 plugin/hpsa/utils.py
Gris Ge
2015-03-14 13:33:04 UTC
Permalink
* Treating HP SmartArray 'Array' term as LSM Pool.

* Depending on output of 'hpssacli ctrl all show config detail'.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 74 insertions(+), 2 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index a02241c..52da272 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -17,10 +17,11 @@

import os
import errno
+import re

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System)
+ System, Pool, size_human_2_size_bytes, search_property)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError

@@ -125,6 +126,38 @@ def _parse_hpssacli_output(output):
return data


+def _hp_size_to_lsm(hp_size):
+ """
+ HP Using 'TB, GB, MB, KB' and etc, for LSM, they are 'TiB' and etc.
+ Return int of block bytes
+ """
+ re_regex = re.compile("^([0-9.]+) +([EPTGMK])B$")
+ re_match = re_regex.match(hp_size)
+ if re_match:
+ return size_human_2_size_bytes(
+ "%s%siB" % (re_match.group(1), re_match.group(2)))
+
+ raise LsmError(
+ ErrorNumber.PLUGIN_BUG,
+ "_hp_size_to_lsm(): Got unexpected HP size string %s" %
+ hp_size)
+
+
+def _pool_status_of(hp_array):
+ """
+ Return (status, status_info)
+ """
+ if hp_array['Status'] == 'OK':
+ return Pool.STATUS_OK, ''
+ else:
+ # TODO(Gris Ge): Try degrade a RAID or fail a RAID.
+ return Pool.STATUS_OTHER, hp_array['Status']
+
+
+def _pool_id_of(sys_id, array_name):
+ return "%s:%s" % (sys_id, array_name.replace(' ', ''))
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -232,7 +265,46 @@ def systems(self, flags=0):

return rc_lsm_syss

+ @staticmethod
+ def _hp_array_to_lsm_pool(hp_array, array_name, sys_id, ctrl_num):
+ pool_id = _pool_id_of(sys_id, array_name)
+ name = array_name
+ elem_type = Pool.ELEMENT_TYPE_VOLUME | Pool.ELEMENT_TYPE_VOLUME_FULL
+ unsupported_actions = 0
+ # TODO(Gris Ge): HP does not provide a precise number of bytes.
+ free_space = _hp_size_to_lsm(hp_array['Unused Space'])
+ total_space = free_space
+ for key_name in hp_array.keys():
+ if key_name.startswith('Logical Drive'):
+ total_space += _hp_size_to_lsm(hp_array[key_name]['Size'])
+
+ (status, status_info) = _pool_status_of(hp_array)
+
+ plugin_data = "%s:%s" % (
+ ctrl_num, array_name[len("Array: "):])
+
+ return Pool(
+ pool_id, name, elem_type, unsupported_actions,
+ total_space, free_space, status, status_info,
+ sys_id, plugin_data)
+
@_handle_errors
def pools(self, search_key=None, search_value=None,
flags=Client.FLAG_RSVD):
- raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported yet")
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_pools = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ lsm_pools.append(
+ SmartArray._hp_array_to_lsm_pool(
+ ctrl_data[key_name], key_name, sys_id, ctrl_num))
+
+ return search_property(lsm_pools, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-14 13:33:06 UTC
Permalink
* Treating HP SmartArray physical disk as LSM disk.

* Depending on output of command "hpssacli ctrl all show config detail"

* Only support Disk.STATUS_FREE, Disk.STATUS_OK and Disk.STATUS_OTHER.
# Seeking server to investigate on spare disk and faulty disks.

* Including physical disk location in Disk.name.

* Set Capabilities.DISKS.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 09530e6..e6bbfdf 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,7 +21,7 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property, Volume)
+ System, Pool, size_human_2_size_bytes, search_property, Volume, Disk)

from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read

@@ -158,6 +158,31 @@ def _pool_id_of(sys_id, array_name):
return "%s:%s" % (sys_id, array_name.replace(' ', ''))


+def _disk_type_of(hp_disk):
+ disk_interface = hp_disk['Interface Type']
+ if disk_interface == 'SATA':
+ return Disk.TYPE_SATA
+ elif disk_interface == 'Solid State SATA':
+ return Disk.TYPE_SSD
+ elif disk_interface == 'SAS':
+ return Disk.TYPE_SAS
+
+ return Disk.TYPE_UNKNOWN
+
+
+def _disk_status_of(hp_disk, flag_free):
+ # TODO(Gris Ge): Need more document or test for non-OK disks.
+ if hp_disk['Status'] == 'OK':
+ disk_status = Disk.STATUS_OK
+ else:
+ disk_status = Disk.STATUS_OTHER
+
+ if flag_free:
+ disk_status |= Disk.STATUS_FREE
+
+ return disk_status
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -224,6 +249,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
"System not found")
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
+ cap.set(Capabilities.DISKS)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -371,3 +397,55 @@ def volumes(self, search_key=None, search_value=None,
array_key_name))

return search_property(lsm_vols, search_key, search_value)
+
+ @staticmethod
+ def _hp_disk_to_lsm_disk(hp_disk, sys_id, ctrl_num, key_name,
+ flag_free=False):
+ disk_id = hp_disk['Serial Number']
+ disk_num = key_name[len("physicaldrive "):]
+ disk_name = "%s %s" % (hp_disk['Model'], disk_num)
+ disk_type = _disk_type_of(hp_disk)
+ blk_size = int(hp_disk['Native Block Size'])
+ blk_count = int(_hp_size_to_lsm(hp_disk['Size']) / blk_size)
+ status = _disk_status_of(hp_disk, flag_free)
+ plugin_data = "%s:%s" % (ctrl_num, disk_num)
+
+ return Disk(
+ disk_id, disk_name, disk_type, blk_size, blk_count,
+ status, sys_id, plugin_data)
+
+ @_handle_errors
+ def disks(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ # TODO(Gris Ge): Need real test on spare disk and free disk.
+ # The free disks is purely base on HP document.
+ rc_lsm_disks = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ sys_id = ctrl_data['Serial Number']
+ ctrl_num = ctrl_data['Slot']
+ for key_name in ctrl_data.keys():
+ if key_name.startswith("Array:"):
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ if key_name == 'unassigned':
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name.startswith("physicaldrive"):
+ rc_lsm_disks.append(
+ SmartArray._hp_disk_to_lsm_disk(
+ ctrl_data[key_name][array_key_name],
+ sys_id, ctrl_num, array_key_name,
+ flag_free=False))
+
+ return search_property(rc_lsm_disks, search_key, search_value)
--
1.8.3.1
Gris Ge
2015-03-14 13:33:05 UTC
Permalink
* Treating HP SmartArray logical drive as LSM volume.

* Depend on output of command "hpssacli ctrl all show config detail".

* New method of utils.py:
* file_read()
# Simply use 'cat' command to do file read which provide better
# error handling using ExecError.

* The block size and block count is read from linux sysfs as hpssacli
provide the sdX name for each logical drive.
Faillback to 512 and calculated from other output entry.

* Include sdX name if found in Volume.name.

* Set Capabilities.VOLUMES.

Signed-off-by: Gris Ge <***@redhat.com>
---
plugin/hpsa/hpsa.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---
plugin/hpsa/utils.py | 8 ++++++
2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index 52da272..09530e6 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -21,9 +21,9 @@

from lsm import (
IPlugin, Client, Capabilities, VERSION, LsmError, ErrorNumber, uri_parse,
- System, Pool, size_human_2_size_bytes, search_property)
+ System, Pool, size_human_2_size_bytes, search_property, Volume)

-from lsm.plugin.hpsa.utils import cmd_exec, ExecError
+from lsm.plugin.hpsa.utils import cmd_exec, ExecError, file_read


def _handle_errors(method):
@@ -217,7 +217,14 @@ def time_out_get(self, flags=Client.FLAG_RSVD):

@_handle_errors
def capabilities(self, system, flags=Client.FLAG_RSVD):
- return Capabilities()
+ cur_lsm_syss = self.systems()
+ if system.id not in list(s.id for s in cur_lsm_syss):
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_SYSTEM,
+ "System not found")
+ cap = Capabilities()
+ cap.set(Capabilities.VOLUMES)
+ return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
"""
@@ -308,3 +315,59 @@ def pools(self, search_key=None, search_value=None,
ctrl_data[key_name], key_name, sys_id, ctrl_num))

return search_property(lsm_pools, search_key, search_value)
+
+ @staticmethod
+ def _hp_ld_to_lsm_vol(hp_ld, pool_id, sys_id, ctrl_num, array_num,
+ hp_ld_name):
+ ld_num = hp_ld_name[len("Logical Drive: "):]
+ vpd83 = hp_ld['Unique Identifier'].lower()
+ # No document or command output indicate block size
+ # of volume. So we try to read from linux kernel, if failed
+ # try 512 and roughly calculate the sector count.
+ regex_match = re.compile("/dev/(sd[a-z]+)").search(hp_ld['Disk Name'])
+ vol_name = hp_ld_name
+ if regex_match:
+ sd_name = regex_match.group(1)
+ block_size = int(file_read(
+ "/sys/block/%s/queue/logical_block_size" % sd_name))
+ num_of_blocks = int(file_read("/sys/block/%s/size" % sd_name))
+ vol_name += ": /dev/%s" % sd_name
+ else:
+ block_size = 512
+ num_of_blocks = int(_hp_size_to_lsm(hp_ld['Size']) / block_size)
+
+ plugin_data = "%s:%s:%s" % (ctrl_num, array_num, ld_num)
+
+ # HP SmartArray does not allow disabling volume.
+ return Volume(
+ vpd83, vol_name, vpd83, block_size, num_of_blocks,
+ Volume.ADMIN_STATE_ENABLED, sys_id, pool_id, plugin_data)
+
+ @_handle_errors
+ def volumes(self, search_key=None, search_value=None,
+ flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl all show config detail
+ """
+ lsm_vols = []
+ ctrl_all_conf = self._sacli_exec(
+ ["ctrl", "all", "show", "config", "detail"])
+ for ctrl_data in ctrl_all_conf.values():
+ ctrl_num = ctrl_data['Slot']
+ sys_id = ctrl_data['Serial Number']
+ for key_name in ctrl_data.keys():
+ if not key_name.startswith("Array:"):
+ continue
+ pool_id = _pool_id_of(sys_id, key_name)
+ array_num = key_name[len('Array: '):]
+ for array_key_name in ctrl_data[key_name].keys():
+ if not array_key_name.startswith("Logical Drive"):
+ continue
+ lsm_vols.append(
+ SmartArray._hp_ld_to_lsm_vol(
+ ctrl_data[key_name][array_key_name],
+ pool_id, sys_id, ctrl_num, array_num,
+ array_key_name))
+
+ return search_property(lsm_vols, search_key, search_value)
diff --git a/plugin/hpsa/utils.py b/plugin/hpsa/utils.py
index 67734b0..52670a0 100644
--- a/plugin/hpsa/utils.py
+++ b/plugin/hpsa/utils.py
@@ -35,6 +35,14 @@ def cmd_exec(cmds):
return str_stdout


+def file_read(file_path):
+ """
+ Read file and return string of file content.
+ Use 'cat' command to better utilize the ExecError.
+ """
+ return cmd_exec(['cat', file_path])
+
+
class ExecError(Exception):
def __init__(self, cmd, errno, stdout, stderr, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
--
1.8.3.1
Gris Ge
2015-03-14 13:33:07 UTC
Permalink
* Depending on output of command 'hpssacli ctrl slot=X show config detail'

* The hpssacli provide strip size and stripe size which is min_io_size and
opt_io_size. They are identical to values of Linux sysfs equivalent
entries.

* Set Capabilities.VOLUME_RAID_INFO.

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

diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index e6bbfdf..ba23849 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
@@ -183,6 +183,28 @@ def _disk_status_of(hp_disk, flag_free):
return disk_status


+def _hp_raid_type_to_lsm(hp_ld):
+ """
+ Based on this property:
+ Fault Tolerance: 0/1/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ if hp_raid_level == '0':
+ return Volume.RAID_TYPE_RAID0
+ elif hp_raid_level == '1':
+ # TODO(Gris Ge): Investigate whether HP has 4 disks RAID 1.
+ # In LSM, that's RAID10.
+ return Volume.RAID_TYPE_RAID1
+ elif hp_raid_level == '5':
+ return Volume.RAID_TYPE_RAID5
+ elif hp_raid_level == '6':
+ return Volume.RAID_TYPE_RAID6
+ elif hp_raid_level == '1+0':
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
class SmartArray(IPlugin):
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
@@ -250,6 +272,7 @@ def capabilities(self, system, flags=Client.FLAG_RSVD):
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUME_RAID_INFO)
return cap

def _sacli_exec(self, sacli_cmds, flag_convert=True):
@@ -449,3 +472,41 @@ def disks(self, search_key=None, search_value=None,
flag_free=False))

return search_property(rc_lsm_disks, search_key, search_value)
+
+ @_handle_errors
+ def volume_raid_info(self, volume, flags=Client.FLAG_RSVD):
+ """
+ Depend on command:
+ hpssacli ctrl slot=0 show config detail
+ """
+ if not volume.plugin_data:
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+ ctrl_data = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"]
+ ).values()[0]
+
+ disk_count = 0
+ for key_name in ctrl_data.keys():
+ if key_name != "Array: %s" % array_num:
+ continue
+ for array_key_name in ctrl_data[key_name].keys():
+ if array_key_name == "Logical Drive: %s" % ld_num:
+ hp_ld = ctrl_data[key_name][array_key_name]
+ raid_type = _hp_raid_type_to_lsm(hp_ld)
+ strip_size = _hp_size_to_lsm(hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(hp_ld['Full Stripe Size'])
+ elif array_key_name.startswith("physicaldrive"):
+ hp_disk = ctrl_data[key_name][array_key_name]
+ if hp_disk['Drive Type'] == 'Data Drive':
+ disk_count += 1
+
+ if disk_count == 0:
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
--
1.8.3.1
Tony Asleson
2015-03-16 20:25:54 UTC
Permalink
Comments below.

Thanks,
Tony
Post by Gris Ge
* Depending on output of command 'hpssacli ctrl slot=X show config detail'
* The hpssacli provide strip size and stripe size which is min_io_size and
opt_io_size. They are identical to values of Linux sysfs equivalent
entries.
* Set Capabilities.VOLUME_RAID_INFO.
---
plugin/hpsa/hpsa.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/plugin/hpsa/hpsa.py b/plugin/hpsa/hpsa.py
index e6bbfdf..ba23849 100644
--- a/plugin/hpsa/hpsa.py
+++ b/plugin/hpsa/hpsa.py
return disk_status
+ """
+ Fault Tolerance: 0/1/5/6/1+0
+ """
+ hp_raid_level = hp_ld['Fault Tolerance']
+ return Volume.RAID_TYPE_RAID0
+ # TODO(Gris Ge): Investigate whether HP has 4 disks RAID 1.
+ # In LSM, that's RAID10.
+ return Volume.RAID_TYPE_RAID1
+ return Volume.RAID_TYPE_RAID5
+ return Volume.RAID_TYPE_RAID6
+ return Volume.RAID_TYPE_RAID10
+
+ return Volume.RAID_TYPE_UNKNOWN
+
+
_DEFAULT_BIN_PATHS = [
"/usr/sbin/hpssacli", "/opt/hp/hpssacli/bld/hpssacli"]
cap = Capabilities()
cap.set(Capabilities.VOLUMES)
cap.set(Capabilities.DISKS)
+ cap.set(Capabilities.VOLUME_RAID_INFO)
return cap
@@ -449,3 +472,41 @@ def disks(self, search_key=None, search_value=None,
flag_free=False))
return search_property(rc_lsm_disks, search_key, search_value)
+
+ """
+ hpssacli ctrl slot=0 show config detail
+ """
+ raise LsmError(
+ ErrorNumber.INVALID_ARGUMENT,
+ "Ilegal input volume argument: missing plugin_data property")
+
+ (ctrl_num, array_num, ld_num) = volume.plugin_data.split(":")
+ ctrl_data = self._sacli_exec(
+ ["ctrl", "slot=%s" % ctrl_num, "show", "config", "detail"]
+ ).values()[0]
+
+ disk_count = 0
Add:
raid_type = Volume.RAID_TYPE_UNKNOWN
strip_size = 0
stripe_size = 0
Post by Gris Ge
+ continue
+ hp_ld = ctrl_data[key_name][array_key_name]
+ raid_type = _hp_raid_type_to_lsm(hp_ld)
+ strip_size = _hp_size_to_lsm(hp_ld['Strip Size'])
+ stripe_size = _hp_size_to_lsm(hp_ld['Full Stripe Size'])
If we execute this path only or don't execute either path...
Post by Gris Ge
+ hp_disk = ctrl_data[key_name][array_key_name]
+ disk_count += 1
+
+ raise LsmError(
+ ErrorNumber.NOT_FOUND_VOLUME,
+ "Volume not found")
+
+ return [raid_type, strip_size, disk_count, strip_size, stripe_size]
is it possible for raid_type, strip_size and stripe_size to not be set?
My preference would be to define these with sane defaults up front just
in case we end up in this case.
Gris Ge
2015-03-14 13:33:08 UTC
Permalink
* Without this, 'git diff' don't trace at method level but class level.

* For more detail, please check:

http://stackoverflow.com/questions/2783086/how-does-git-diff-generate-hunk-descriptions

Signed-off-by: Gris Ge <***@redhat.com>
---
.gitattributes | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .gitattributes

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..95105a3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.py diff=python
--
1.8.3.1
Loading...