diff --git a/pyproject.toml b/pyproject.toml index 689d232..9192ccf 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] @@ -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 977c7aa..4e64faa 100644 --- a/pystorcli2/controller/__init__.py +++ b/pystorcli2/controller/__init__.py @@ -101,6 +101,36 @@ def name(self): """ return self._name + @ property + def show(self): + """Caching show output to allow getting info of static attributes + 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|None): get serial number if exists + """ + return self.show.get('Serial Number') + + @ property + def model(self): + """ (str|None): get model if exists + """ + return self.show.get('Model') + + @ property + def pci_address(self): + """ (str|None): get pci address if exists + """ + return self.show.get('PCI Address') + @property def facts(self): """ (dict): raw controller facts @@ -503,6 +533,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 @@ -528,16 +600,28 @@ def __init__(self, binary='storcli64'): self._binary = binary self._storcli = StorCLI(binary) + @ property + def show(self) -> List[dict]: + """Caching show output to allow getting info of static attributes + Return: + list: list of dicts of controllers data from show command + """ + 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]: - 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'])] + """(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 1ccaa03..38bcf7d 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 @@ -104,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) @@ -146,12 +154,29 @@ def metrics(self): @property def size(self): - """(str): drive size + """(str): drive size in bytes (pysmart compliance) + """ + args = [ + 'show' + ] + 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(self.size) + + @property + @common.upper + def block_size(self): + """(str): block size 4KB / 512B """ args = [ 'show' ] - return self._response_properties(self._run(args))['Size'] + 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..961a28b 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 @@ -85,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) @@ -143,12 +151,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(self.size) @property def state(self) -> VDState: