From 238d2844ccfdd7cec005a302cb20757aa95a5d06 Mon Sep 17 00:00:00 2001 From: "d.ulybin" Date: Wed, 23 Aug 2023 05:32:35 +0300 Subject: [PATCH 1/6] Suggesting some new methods --- pystorcli2/controller/__init__.py | 56 +++++++++++++++++++++++++++-- pystorcli2/drive/__init__.py | 22 ++++++++++-- pystorcli2/storcli.py | 2 +- pystorcli2/virtualdrive/__init__.py | 12 +++++-- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/pystorcli2/controller/__init__.py b/pystorcli2/controller/__init__.py index 977c7aa..7c1f37e 100644 --- a/pystorcli2/controller/__init__.py +++ b/pystorcli2/controller/__init__.py @@ -503,6 +503,48 @@ def import_foreign_configurations(self, securitykey: Optional[str] = None): args.append(f'securitykey={securitykey}') return common.response_cmd(self._run(args)) + def set_controller_mode(self, mode): + """Set controller mode command + + Returns: + (dict): response cmd data + """ + args = [ + 'set', + f'personality={mode}' + ] + return common.response_cmd(self._run(args)) + + def delete_vds(self): + """Delete all virtual drives + + Returns: + (dict): response cmd data + """ + args = [ + '/vall', + 'delete' + ] + return common.response_cmd(self._run(args)) + + @property + def phyerrorcounters(self): + """Get Phy Error Counters + + Returns: + (dict): response cmd data + """ + args = [ + '/pall', + 'show' + ] + try: + # RAID + return common.response_data(self._run(args + ['all']))['Phy Error Counters'] + except exc.StorCliCmdError: + # HBA + return common.response_data(self._run(args + ['phyerrorcounters'])) + class Controllers(object): """StorCLI Controllers @@ -529,15 +571,23 @@ def __init__(self, binary='storcli64'): self._storcli = StorCLI(binary) @ property - def _ctl_ids(self) -> List[int]: + def show(self) -> List[dict]: + """ + Return: + list: list of dicts of controllers data from show command + """ out = self._storcli.run(['show'], allow_error_codes=[ StorcliErrorCode.INCOMPLETE_FOREIGN_CONFIGURATION]) response = common.response_data(out) if "Number of Controllers" in response and response["Number of Controllers"] == 0: return [] - else: - return [ctl['Ctl'] for ctl in common.response_data_subkey(out, ['System Overview', 'IT System Overview'])] + return common.response_data_subkey(out, ['System Overview', 'IT System Overview']) + + @ property + def _ctl_ids(self) -> List[int]: + out = self.show + return out if not out else [ctl['Ctl'] for ctl in out] @ property def _ctls(self): diff --git a/pystorcli2/drive/__init__.py b/pystorcli2/drive/__init__.py index 1ccaa03..395d7d7 100644 --- a/pystorcli2/drive/__init__.py +++ b/pystorcli2/drive/__init__.py @@ -6,6 +6,7 @@ '''StorCLI drive python module ''' +import humanfriendly from .. import StorCLI from .. import common @@ -146,12 +147,29 @@ def metrics(self): @property def size(self): - """(str): drive size + """(str): drive size in bytes (pysmart compliance) """ args = [ 'show' ] - return self._response_properties(self._run(args))['Size'] + size = self._response_properties(self._run(args))['Size'] + return humanfriendly.parse_size(size) + + @property + def capacity(self): + """Size in human readable format (pysmart compliance) + """ + return humanfriendly.format_size(getattr(self, 'size', '')) + + @property + @common.upper + def block_size(self): + """(str): block size 4KB / 512B + """ + args = [ + 'show' + ] + return self._response_properties(self._run(args))['SeSz'].replace(' ', '').strip() @property @common.upper diff --git a/pystorcli2/storcli.py b/pystorcli2/storcli.py index e7e6236..3f8d23e 100644 --- a/pystorcli2/storcli.py +++ b/pystorcli2/storcli.py @@ -34,7 +34,7 @@ class StorCLI(object): binary (str): storcli binary or full path to the binary Properties: - cache_enable (boolean): enable disable resposne cache (also setter) + cache_enable (boolean): enable disable response cache (also setter) cache (dict): get / set raw cache content Methods: diff --git a/pystorcli2/virtualdrive/__init__.py b/pystorcli2/virtualdrive/__init__.py index 2e583d3..3a97490 100644 --- a/pystorcli2/virtualdrive/__init__.py +++ b/pystorcli2/virtualdrive/__init__.py @@ -6,6 +6,7 @@ '''StorCLI virtual virtual drive python module ''' +import humanfriendly from .. import StorCLI from .. import common @@ -143,12 +144,19 @@ def raid(self): @property def size(self): - """(str): virtual drive size + """(str): virtual drive size in bytes (pysmart compliance) """ args = [ 'show' ] - return self._response_properties(self._run(args))['Size'] + size = self._response_properties(self._run(args))['Size'] + return humanfriendly.parse_size(size) + + @property + def capacity(self): + """Size in human readable format (pysmart compliance) + """ + return humanfriendly.format_size(getattr(self, 'size', '')) @property def state(self) -> VDState: From bc796048a08a94779cb46b3292d5656cdb439e8d Mon Sep 17 00:00:00 2001 From: ulmitov Date: Wed, 23 Aug 2023 16:47:34 +0300 Subject: [PATCH 2/6] Show command property to avoid playing with cache responses --- pystorcli2/controller/__init__.py | 56 ++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/pystorcli2/controller/__init__.py b/pystorcli2/controller/__init__.py index 7c1f37e..aff80b5 100644 --- a/pystorcli2/controller/__init__.py +++ b/pystorcli2/controller/__init__.py @@ -101,6 +101,43 @@ def name(self): """ return self._name + @ property + def show(self) -> dict: + """Static show output to allow getting info of static attributes + without the need to touch cached responses + Return: + dict: controller data from show command + """ + if not getattr(self, '_show', None): + out = self._storcli.run(['show'], allow_error_codes=[ + StorcliErrorCode.INCOMPLETE_FOREIGN_CONFIGURATION]) + self._show = common.response_data(out) + return self._show + + @ property + def serial(self): + """ (str): get serial number + """ + return self.show['Serial Number'] + + @ property + def model(self): + """ (str): get model + """ + return self.show['Product Name'] + + @ property + def pci_address(self): + """ (str): get pci address + """ + return self.show['PCI Address'] + + @ property + def sas_address(self): + """ (str): get sas address + """ + return self.show['SAS Address'] + @property def facts(self): """ (dict): raw controller facts @@ -572,17 +609,20 @@ def __init__(self, binary='storcli64'): @ property def show(self) -> List[dict]: - """ + """Static show output to allow getting info of static attributes + without the need to touch cached responses Return: list: list of dicts of controllers data from show command """ - out = self._storcli.run(['show'], allow_error_codes=[ - StorcliErrorCode.INCOMPLETE_FOREIGN_CONFIGURATION]) - response = common.response_data(out) - - if "Number of Controllers" in response and response["Number of Controllers"] == 0: - return [] - return common.response_data_subkey(out, ['System Overview', 'IT System Overview']) + if not getattr(self, '_show', None): + out = self._storcli.run(['show'], allow_error_codes=[ + StorcliErrorCode.INCOMPLETE_FOREIGN_CONFIGURATION]) + response = common.response_data(out) + if "Number of Controllers" in response and response["Number of Controllers"] == 0: + self._show = [] + else: + self._show = common.response_data_subkey(out, ['System Overview', 'IT System Overview']) + return self._show @ property def _ctl_ids(self) -> List[int]: From 5c58834b7d9ac071ead2c17c475c58cb6b170876 Mon Sep 17 00:00:00 2001 From: ulmitov Date: Wed, 23 Aug 2023 16:53:49 +0300 Subject: [PATCH 3/6] Fix to return empty str just in case prop is missing --- pystorcli2/controller/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pystorcli2/controller/__init__.py b/pystorcli2/controller/__init__.py index aff80b5..0dc913c 100644 --- a/pystorcli2/controller/__init__.py +++ b/pystorcli2/controller/__init__.py @@ -102,7 +102,7 @@ def name(self): return self._name @ property - def show(self) -> dict: + def show(self): """Static show output to allow getting info of static attributes without the need to touch cached responses Return: @@ -118,25 +118,25 @@ def show(self) -> dict: def serial(self): """ (str): get serial number """ - return self.show['Serial Number'] + return self.show.get('Serial Number', '') @ property def model(self): """ (str): get model """ - return self.show['Product Name'] + return self.show.get('Product Name', '') @ property def pci_address(self): """ (str): get pci address """ - return self.show['PCI Address'] + return self.show.get('PCI Address', '') @ property def sas_address(self): """ (str): get sas address """ - return self.show['SAS Address'] + return self.show.get('SAS Address', '') @property def facts(self): From 150ad2e87498a70886ae4fcc8df1f07124160a32 Mon Sep 17 00:00:00 2001 From: Rafael Leira Date: Thu, 24 Aug 2023 12:22:18 +0200 Subject: [PATCH 4/6] added dep humanfriendly --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 689d232..c2eb61a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ classifiers = [ 'Programming Language :: Python', 'Development Status :: 3 - Alpha', ] -dependencies = [] +dependencies = ['humanfriendly'] dynamic = ["version"] [project.urls] From c87f581f69326af8da592f52abcd26b02be2d457 Mon Sep 17 00:00:00 2001 From: Rafael Leira Date: Thu, 24 Aug 2023 12:40:36 +0200 Subject: [PATCH 5/6] mypy fixes --- pyproject.toml | 9 ++++++++- pystorcli2/controller/__init__.py | 9 +++++---- pystorcli2/drive/__init__.py | 2 +- pystorcli2/virtualdrive/__init__.py | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c2eb61a..9192ccf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,4 +47,11 @@ pystorcli-metrics = "pystorcli2.bin.metrics:main" [project.optional-dependencies] # Requirements only needed for development -dev = ['pytest', 'pytest-cov', 'coveralls', 'pdoc', 'mypy'] +dev = [ + 'coveralls', + 'mypy', + 'pdoc', + 'pytest-cov', + 'pytest', + 'types-humanfriendly', +] diff --git a/pystorcli2/controller/__init__.py b/pystorcli2/controller/__init__.py index 0dc913c..36c6e29 100644 --- a/pystorcli2/controller/__init__.py +++ b/pystorcli2/controller/__init__.py @@ -113,7 +113,7 @@ def show(self): StorcliErrorCode.INCOMPLETE_FOREIGN_CONFIGURATION]) self._show = common.response_data(out) return self._show - + @ property def serial(self): """ (str): get serial number @@ -131,7 +131,7 @@ def pci_address(self): """ (str): get pci address """ return self.show.get('PCI Address', '') - + @ property def sas_address(self): """ (str): get sas address @@ -621,13 +621,14 @@ def show(self) -> List[dict]: if "Number of Controllers" in response and response["Number of Controllers"] == 0: self._show = [] else: - self._show = common.response_data_subkey(out, ['System Overview', 'IT System Overview']) + self._show = common.response_data_subkey( + out, ['System Overview', 'IT System Overview']) return self._show @ property def _ctl_ids(self) -> List[int]: out = self.show - return out if not out else [ctl['Ctl'] for ctl in out] + return [] if not out else [ctl['Ctl'] for ctl in out] @ property def _ctls(self): diff --git a/pystorcli2/drive/__init__.py b/pystorcli2/drive/__init__.py index 395d7d7..5c9c78d 100644 --- a/pystorcli2/drive/__init__.py +++ b/pystorcli2/drive/__init__.py @@ -159,7 +159,7 @@ def size(self): def capacity(self): """Size in human readable format (pysmart compliance) """ - return humanfriendly.format_size(getattr(self, 'size', '')) + return humanfriendly.format_size(self.size) @property @common.upper diff --git a/pystorcli2/virtualdrive/__init__.py b/pystorcli2/virtualdrive/__init__.py index 3a97490..c4d8f21 100644 --- a/pystorcli2/virtualdrive/__init__.py +++ b/pystorcli2/virtualdrive/__init__.py @@ -156,7 +156,7 @@ def size(self): def capacity(self): """Size in human readable format (pysmart compliance) """ - return humanfriendly.format_size(getattr(self, 'size', '')) + return humanfriendly.format_size(self.size) @property def state(self) -> VDState: From e81932e2e23993362655e8391f59b77a47abce2d Mon Sep 17 00:00:00 2001 From: ulmitov Date: Thu, 24 Aug 2023 17:50:33 +0300 Subject: [PATCH 6/6] Some logic format fixes and repr methods --- pystorcli2/controller/__init__.py | 29 +++++++++++------------------ pystorcli2/drive/__init__.py | 9 ++++++++- pystorcli2/virtualdrive/__init__.py | 9 ++++++++- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/pystorcli2/controller/__init__.py b/pystorcli2/controller/__init__.py index 0dc913c..22752fc 100644 --- a/pystorcli2/controller/__init__.py +++ b/pystorcli2/controller/__init__.py @@ -103,8 +103,7 @@ def name(self): @ property def show(self): - """Static show output to allow getting info of static attributes - without the need to touch cached responses + """Caching show output to allow getting info of static attributes Return: dict: controller data from show command """ @@ -116,27 +115,21 @@ def show(self): @ property def serial(self): - """ (str): get serial number + """ (str|None): get serial number if exists """ - return self.show.get('Serial Number', '') + return self.show.get('Serial Number') @ property def model(self): - """ (str): get model + """ (str|None): get model if exists """ - return self.show.get('Product Name', '') + return self.show.get('Model') @ property def pci_address(self): - """ (str): get pci address + """ (str|None): get pci address if exists """ - return self.show.get('PCI Address', '') - - @ property - def sas_address(self): - """ (str): get sas address - """ - return self.show.get('SAS Address', '') + return self.show.get('PCI Address') @property def facts(self): @@ -609,8 +602,7 @@ def __init__(self, binary='storcli64'): @ property def show(self) -> List[dict]: - """Static show output to allow getting info of static attributes - without the need to touch cached responses + """Caching show output to allow getting info of static attributes Return: list: list of dicts of controllers data from show command """ @@ -626,8 +618,9 @@ def show(self) -> List[dict]: @ property def _ctl_ids(self) -> List[int]: - out = self.show - return out if not out else [ctl['Ctl'] for ctl in out] + """(list of str): controllers id + """ + return [ctl['Ctl'] for ctl in self.show] @ property def _ctls(self): diff --git a/pystorcli2/drive/__init__.py b/pystorcli2/drive/__init__.py index 395d7d7..d5d30f1 100644 --- a/pystorcli2/drive/__init__.py +++ b/pystorcli2/drive/__init__.py @@ -105,6 +105,13 @@ def _response_attributes(self, out): self._ctl_id, self._encl_id, self._slot_id) return common.response_data(out)[detailed_info][attr] + def __repr__(self): + """Define a basic representation of the class object.""" + return ''.format( + self._name, + ' '.join([self.serial, self.medium, self.state]) + ) + def _run(self, args, **kwargs): args = args[:] args.insert(0, self._name) @@ -159,7 +166,7 @@ def size(self): def capacity(self): """Size in human readable format (pysmart compliance) """ - return humanfriendly.format_size(getattr(self, 'size', '')) + return humanfriendly.format_size(getattr(self, 'size', 0)) @property @common.upper diff --git a/pystorcli2/virtualdrive/__init__.py b/pystorcli2/virtualdrive/__init__.py index 3a97490..edc5f8a 100644 --- a/pystorcli2/virtualdrive/__init__.py +++ b/pystorcli2/virtualdrive/__init__.py @@ -86,6 +86,13 @@ def __init__(self, ctl_id, vd_id, binary='storcli64'): self._exist() + def __repr__(self): + """Define a basic representation of the class object.""" + return ''.format( + self._name, + ' '.join([self.raid, self.capacity, self.state]) + ) + def _run(self, args, **kwargs): args = args[:] args.insert(0, self._name) @@ -156,7 +163,7 @@ def size(self): def capacity(self): """Size in human readable format (pysmart compliance) """ - return humanfriendly.format_size(getattr(self, 'size', '')) + return humanfriendly.format_size(getattr(self, 'size', 0)) @property def state(self) -> VDState: