diff --git a/proliantutils/hpssa/objects.py b/proliantutils/hpssa/objects.py index 61bfd693..4b0b36be 100644 --- a/proliantutils/hpssa/objects.py +++ b/proliantutils/hpssa/objects.py @@ -37,7 +37,7 @@ def _get_key_value(string): key = '' value = '' try: - key, value = string.split(':') + key, value = string.split(': ') except ValueError: # This handles the case when the property of a logical drive # returned is as follows. Here we cannot split by ':' because @@ -47,16 +47,18 @@ def _get_key_value(string): string = string.lstrip(' ') if string.startswith('physicaldrive'): fields = string.split(' ') - key = fields[0] + # Include fields[1] to key to avoid duplicate pairs + # with the same 'physicaldrive' key + key = fields[0] + " " + fields[1] value = fields[1] else: # TODO(rameshg87): Check if this ever occurs. - return None, None + return string.strip(' '), None - return key.lstrip(' ').rstrip(' '), value.lstrip(' ').rstrip(' ') + return key.strip(' '), value.strip(' ') -def _get_dict(lines, start_index, indentation): +def _get_dict(lines, start_index, indentation, deep): """Recursive function for parsing hpssacli/ssacli output.""" info = {} @@ -68,37 +70,38 @@ def _get_dict(lines, start_index, indentation): current_line = lines[i] current_line_indentation = _get_indentation(current_line) + # Check for multi-level returns + if current_line_indentation < indentation: + return info, i-1 + if current_line_indentation == indentation: current_item = current_line.lstrip(' ') info[current_item] = {} i = i + 1 continue - if i >= len(lines) - 1: + if i < len(lines) - 1: + next_line_indentation = _get_indentation(lines[i+1]) + else: + next_line_indentation = current_line_indentation + + if next_line_indentation > current_line_indentation: + ret_dict, i = _get_dict(lines, i, current_line_indentation, deep+1) + for key in ret_dict.keys(): + if key in info[current_item]: + info[current_item][key].update(ret_dict[key]) + else: + info[current_item][key] = ret_dict[key] + else: key, value = _get_key_value(current_line) - # If this is some unparsable information, then - # just skip it. if key: info[current_item][key] = value + + # Do not return if it's the top level of recursion + if next_line_indentation < current_line_indentation and deep > 0: return info, i - next_line = lines[i+1] - next_line_indentation = _get_indentation(next_line) - - if current_line_indentation == next_line_indentation: - key, value = _get_key_value(current_line) - if key: - info[current_item][key] = value - i = i + 1 - elif next_line_indentation > current_line_indentation: - ret_dict, j = _get_dict(lines, i, current_line_indentation) - info[current_item].update(ret_dict) - i = j + 1 - elif next_line_indentation < current_line_indentation: - key, value = _get_key_value(current_line) - if key: - info[current_item][key] = value - return info, i + i = i + 1 return info, i @@ -113,7 +116,7 @@ def _convert_to_dict(stdout): lines = stdout.split("\n") lines = list(filter(None, lines)) - info_dict, j = _get_dict(lines, 0, 0) + info_dict, j = _get_dict(lines, 0, 0, 0) return info_dict @@ -556,14 +559,22 @@ class LogicalDrive(object): # 'string_to_bytes' takes care of converting any returned # (like 500MB, 25GB) unit of storage space to bytes (Integer value). # It requires space to be stripped. - size = self.properties['Size'].replace(' ', '') try: + size = self.properties['Size'].replace(' ', '') # TODO(rameshg87): Reduce the disk size by 1 to make sure Ironic # has enough space to write a config drive. Remove this when # Ironic doesn't need it. self.size_gb = int(strutils.string_to_bytes(size, return_int=True) / (1024*1024*1024)) - 1 + except KeyError: + msg = ("Can't get 'Size' parameter from ssacli output for logical " + "disk '%(logical_disk)s' of RAID array '%(array)s' in " + "controller '%(controller)s'." % + {'logical_disk': self.id, + 'array': self.parent.id, + 'controller': self.parent.parent.id}) + raise exception.HPSSAOperationError(reason=msg) except ValueError: msg = ("ssacli returned unknown size '%(size)s' for logical " "disk '%(logical_disk)s' of RAID array '%(array)s' in " @@ -617,14 +628,21 @@ class PhysicalDrive(object): # Strip off physicaldrive before storing it in id self.id = id[14:] - size = self.properties['Size'].replace(' ', '') # 'string_to_bytes' takes care of converting any returned # (like 500MB, 25GB) unit of storage space to bytes (Integer value). # It requires space to be stripped. try: + size = self.properties['Size'].replace(' ', '') self.size_gb = int(strutils.string_to_bytes(size, return_int=True) / (1024*1024*1024)) + except KeyError: + msg = ("Can't get 'Size' parameter from ssacli output for " + "physical disk '%(physical_disk)s' of controller " + "'%(controller)s'." % + {'physical_disk': self.id, + 'controller': self.parent.parent.id}) + raise exception.HPSSAOperationError(reason=msg) except ValueError: msg = ("ssacli returned unknown size '%(size)s' for physical " "disk '%(physical_disk)s' of controller " @@ -633,7 +651,16 @@ class PhysicalDrive(object): 'controller': self.parent.id}) raise exception.HPSSAOperationError(reason=msg) - ssa_interface = self.properties['Interface Type'] + try: + ssa_interface = self.properties['Interface Type'] + except KeyError: + msg = ("Can't get 'Interface Type' parameter from ssacli output " + "for physical disk '%(physical_disk)s' of controller " + "'%(controller)s'." % + {'physical_disk': self.id, + 'controller': self.parent.parent.id}) + raise exception.HPSSAOperationError(reason=msg) + self.interface_type = constants.get_interface_type(ssa_interface) self.disk_type = constants.get_disk_type(ssa_interface) self.model = self.properties.get('Model') diff --git a/proliantutils/tests/hpssa/raid_constants.py b/proliantutils/tests/hpssa/raid_constants.py index cdb9780e..c45ecffc 100644 --- a/proliantutils/tests/hpssa/raid_constants.py +++ b/proliantutils/tests/hpssa/raid_constants.py @@ -2507,3 +2507,62 @@ Smart Array P440 in Slot 2 Sanitize Estimated Max Erase Time: 0 hour(s)36 minute(s) Unrestricted Sanitize Supported: False ''' + +SSACLI_PARSING_TESTS = ''' +Smart HBA H240 in Slot 1 (RAID Mode) + Slot: 1 + Controller Mode: RAID Mode + + Internal Drive Cage at Port 1I, Box 1, OK + Drive Bays: 4 + Port: 1I + Box: 1 + + Physical Drives + physicaldrive 1I:1:4 (port 1I:box 1:bay 4, SAS HDD, 900 GB, OK) + physicaldrive 1I:1:3 (port 1I:box 1:bay 3, SAS HDD, 900 GB, OK) + + Internal Drive Cage at Port 2I, Box 1, OK + Drive Bays: 4 + Port: 2I + Box: 1 + + Physical Drives + physicaldrive 2I:1:5 (port 2I:box 1:bay 5, SAS HDD, 900 GB, OK) + physicaldrive 2I:1:6 (port 2I:box 1:bay 6, SAS HDD, 900 GB, OK) + + Unassigned + physicaldrive 1I:1:4 + Port: 1I + Box: 1 + Bay: 4 + Size: 900 GB + Interface Type: SAS + +Smart HBA H240 in Slot 2 (RAID Mode) + Slot: 2 + Controller Mode: RAID Mode + PCI Address (Domain:Bus:Device.Function): 0000:0B:00.0 + + Array: H + Interface Type: SAS + + Logical Drive: 8 + Size: 838.3 GB + Status: OK + + physicaldrive 2I:2:8 + Port: 2I + Box: 2 + Bay: 8 + Size: 900 GB + Interface Type: SAS + +Smart HBA H240 in Slot 3 (RAID Mode) + Slot: 3 + Controller Mode: RAID Mode + +Smart HBA H240ar in Slot 0 (Embedded) (RAID Mode) + Bus Interface: PCI + Slot: 0 +''' diff --git a/proliantutils/tests/hpssa/test_objects.py b/proliantutils/tests/hpssa/test_objects.py index b06df280..c9cfee02 100644 --- a/proliantutils/tests/hpssa/test_objects.py +++ b/proliantutils/tests/hpssa/test_objects.py @@ -606,6 +606,25 @@ class PhysicalDriveTest(testtools.TestCase): self.assertEqual('ready', ret['status']) self.assertEqual('OK', ret['erase_status']) + def test_ssacli_output_parsing(self, get_all_details_mock): + + get_all_details_mock.return_value = raid_constants.SSACLI_PARSING_TESTS + server = objects.Server() + self.assertEqual(4, len(server.controllers)) + id = 'Smart HBA H240ar in Slot 0 (Embedded) (RAID Mode)' + self.assertIsNotNone(server.get_controller_by_id(id)) + + id = 'Smart HBA H240 in Slot 2 (RAID Mode)' + controller = server.get_controller_by_id(id) + self.assertIsInstance(controller.properties, dict) + self.assertIn("PCI Address (Domain:Bus:Device.Function)", + controller.properties) + + id = 'Smart HBA H240 in Slot 1 (RAID Mode)' + controller = server.get_controller_by_id(id) + self.assertIsInstance(controller.properties, dict) + self.assertEqual(4, len(controller.properties['Physical Drives'])) + class PrivateMethodsTestCase(testtools.TestCase):