diff --git a/dracclient/resources/raid.py b/dracclient/resources/raid.py index d16501e..17de591 100644 --- a/dracclient/resources/raid.py +++ b/dracclient/resources/raid.py @@ -113,7 +113,7 @@ VirtualDiskTuple = collections.namedtuple( 'VirtualDisk', ['id', 'name', 'description', 'controller', 'raid_level', 'size_mb', 'status', 'raid_status', 'span_depth', 'span_length', - 'pending_operations']) + 'pending_operations', 'physical_disks']) class VirtualDisk(VirtualDiskTuple): @@ -239,13 +239,19 @@ class RAIDManagement(object): span_length=int(self._get_virtual_disk_attr(drac_disk, 'SpanLength')), pending_operations=( - VIRTUAL_DISK_PENDING_OPERATIONS[drac_pending_operations])) + VIRTUAL_DISK_PENDING_OPERATIONS[drac_pending_operations]), + physical_disks=self._get_virtual_disk_attrs(drac_disk, + 'PhysicalDiskIDs')) def _get_virtual_disk_attr(self, drac_disk, attr_name, nullable=False): return utils.get_wsman_resource_attr( drac_disk, uris.DCIM_VirtualDiskView, attr_name, nullable=nullable) + def _get_virtual_disk_attrs(self, drac_disk, attr_name): + return utils.get_all_wsman_resource_attrs( + drac_disk, uris.DCIM_VirtualDiskView, attr_name, nullable=False) + def list_physical_disks(self): """Returns the list of physical disks diff --git a/dracclient/tests/test_raid.py b/dracclient/tests/test_raid.py index f802db8..c4ce680 100644 --- a/dracclient/tests/test_raid.py +++ b/dracclient/tests/test_raid.py @@ -62,7 +62,11 @@ class ClientRAIDManagementTestCase(base.BaseTest): raid_status='online', span_depth=1, span_length=2, - pending_operations=None) + pending_operations=None, + physical_disks=[ + 'Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1', + 'Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1' + ]) mock_requests.post( 'https://1.2.3.4:443/wsman', @@ -452,7 +456,7 @@ class ClientRAIDManagementTestCase(base.BaseTest): @mock.patch.object(dracclient.resources.job.JobManagement, 'delete_pending_config', spec_set=True, autospec=True) - def test_abandon_pending_bios_changes(self, mock_requests, + def test_abandon_pending_raid_changes(self, mock_requests, mock_delete_pending_config): self.drac_client.abandon_pending_raid_changes('controller') diff --git a/dracclient/tests/test_utils.py b/dracclient/tests/test_utils.py index e99db8b..cad2744 100644 --- a/dracclient/tests/test_utils.py +++ b/dracclient/tests/test_utils.py @@ -27,6 +27,28 @@ class UtilsTestCase(base.BaseTest): def setUp(self): super(UtilsTestCase, self).setUp() + def test__is_attr_non_nil_True(self): + doc = etree.fromstring( + test_utils.RAIDEnumerations[ + uris.DCIM_ControllerView]['ok']) + controllers = utils.find_xml(doc, 'DCIM_ControllerView', + uris.DCIM_ControllerView, find_all=True) + version = utils.find_xml(controllers[0], 'Bus', + uris.DCIM_ControllerView) + + self.assertTrue(utils._is_attr_non_nil(version)) + + def test__is_attr_non_nil_False(self): + doc = etree.fromstring( + test_utils.RAIDEnumerations[ + uris.DCIM_ControllerView]['ok']) + controllers = utils.find_xml(doc, 'DCIM_ControllerView', + uris.DCIM_ControllerView, find_all=True) + version = utils.find_xml(controllers[0], 'DriverVersion', + uris.DCIM_ControllerView) + + self.assertFalse(utils._is_attr_non_nil(version)) + def test_get_wsman_resource_attr(self): doc = etree.fromstring( test_utils.InventoryEnumerations[uris.DCIM_CPUView]['ok']) @@ -79,3 +101,68 @@ class UtilsTestCase(base.BaseTest): exceptions.DRACEmptyResponseField, re.escape(expected_message), utils.get_wsman_resource_attr, cpus[0], uris.DCIM_CPUView, 'HyperThreadingEnabled', allow_missing=False) + + def test_get_wsman_resource_attr_missing_text_allowed(self): + doc = etree.fromstring( + test_utils.RAIDEnumerations[ + uris.DCIM_ControllerView]['ok']) + controllers = utils.find_xml(doc, 'DCIM_ControllerView', + uris.DCIM_ControllerView, find_all=True) + + result = utils.get_wsman_resource_attr( + controllers[0], uris.DCIM_ControllerView, 'DriverVersion', + allow_missing=False, nullable=True) + self.assertIsNone(result) + + def test_get_all_wsman_resource_attrs(self): + doc = etree.fromstring( + test_utils.RAIDEnumerations[uris.DCIM_VirtualDiskView]['ok']) + vdisks = utils.find_xml(doc, 'DCIM_VirtualDiskView', + uris.DCIM_VirtualDiskView, find_all=True) + + vals = utils.get_all_wsman_resource_attrs( + vdisks[0], uris.DCIM_VirtualDiskView, 'PhysicalDiskIDs') + + expected_pdisks = [ + 'Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1', + 'Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1' + ] + self.assertListEqual(expected_pdisks, vals) + + def test_get_all_wsman_resource_attrs_missing_attr_allowed(self): + doc = etree.fromstring( + test_utils.InventoryEnumerations[ + uris.DCIM_CPUView]['missing_flags']) + cpus = utils.find_xml(doc, 'DCIM_CPUView', uris.DCIM_CPUView, + find_all=True) + + vals = utils.get_all_wsman_resource_attrs( + cpus[0], uris.DCIM_CPUView, 'HyperThreadingEnabled') + + self.assertListEqual([], vals) + + def test_get_all_wsman_resource_attrs_missing_text(self): + expected_message = ("Attribute 'HyperThreadingEnabled' is not nullable" + ", but no value received") + doc = etree.fromstring( + test_utils.InventoryEnumerations[ + uris.DCIM_CPUView]['empty_flag']) + cpus = utils.find_xml(doc, 'DCIM_CPUView', uris.DCIM_CPUView, + find_all=True) + + self.assertRaisesRegexp( + exceptions.DRACEmptyResponseField, re.escape(expected_message), + utils.get_all_wsman_resource_attrs, cpus[0], uris.DCIM_CPUView, + 'HyperThreadingEnabled') + + def test_get_all_wsman_resource_attrs_missing_text_allowed(self): + doc = etree.fromstring( + test_utils.RAIDEnumerations[ + uris.DCIM_ControllerView]['ok']) + controllers = utils.find_xml(doc, 'DCIM_ControllerView', + uris.DCIM_ControllerView, find_all=True) + + result = utils.get_all_wsman_resource_attrs( + controllers[0], uris.DCIM_ControllerView, 'DriverVersion', + nullable=True) + self.assertEqual(result, []) diff --git a/dracclient/tests/wsman_mocks/virtual_disk_view-enum-ok.xml b/dracclient/tests/wsman_mocks/virtual_disk_view-enum-ok.xml index 1777050..ee507f2 100644 --- a/dracclient/tests/wsman_mocks/virtual_disk_view-enum-ok.xml +++ b/dracclient/tests/wsman_mocks/virtual_disk_view-enum-ok.xml @@ -30,7 +30,8 @@ Background Intialization 8 0 - + Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1 + Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1 1 2 4 @@ -51,4 +52,4 @@ - \ No newline at end of file + diff --git a/dracclient/utils.py b/dracclient/utils.py index 935cdb4..c7ddc82 100644 --- a/dracclient/utils.py +++ b/dracclient/utils.py @@ -46,6 +46,15 @@ def find_xml(doc, item, namespace, find_all=False): return doc.find(query) +def _is_attr_non_nil(elem): + """Return whether an element is non-nil. + + :param elem: the element object. + :returns: whether the element is nil. + """ + return elem.attrib.get('{%s}nil' % NS_XMLSchema_Instance) != 'true' + + def get_wsman_resource_attr(doc, resource_uri, attr_name, nullable=False, allow_missing=False): """Find an attribute of a resource in an ElementTree object. @@ -78,11 +87,37 @@ def get_wsman_resource_attr(doc, resource_uri, attr_name, nullable=False, raise exceptions.DRACEmptyResponseField(attr=attr_name) return item.text.strip() else: - nil_attr = item.attrib.get('{%s}nil' % NS_XMLSchema_Instance) - if nil_attr != 'true': + if _is_attr_non_nil(item): return item.text.strip() +def get_all_wsman_resource_attrs(doc, resource_uri, attr_name, nullable=False): + """Find all instances of an attribute of a resource in an ElementTree. + + :param doc: the element tree object. + :param resource_uri: the resource URI of the namespace. + :param attr_name: the name of the attribute. + :param nullable: enables checking if any of the elements contain an + XMLSchema-instance namespaced nil attribute that has a + value of True. In this case, these elements will not be + returned. + :raises: DRACEmptyResponseField if any of the attributes in the XML doc + have no text and nullable is False. + :returns: a list containing the value of each of the instances of the + attribute. + """ + items = find_xml(doc, attr_name, resource_uri, find_all=True) + + if not nullable: + for item in items: + if item.text is None: + raise exceptions.DRACEmptyResponseField(attr=attr_name) + return [item.text.strip() for item in items] + else: + + return [item.text.strip() for item in items if _is_attr_non_nil(item)] + + def is_reboot_required(doc, resource_uri): """Check the response document if reboot is requested.