Add constituent physical disks to RAID virtual disk object

This change adds a new attribute - physical_disks - to
dracclient.resources.raid.VirtualDisk objects. The attribute is a
list of IDs of the physical disks that a virtual disk is composed of.
This allows clients of the library to obtain a more complete view of
the current RAID configuration.

Change-Id: If4f762d3bc115a971d0392af8270f5440ef43913
Closes-bug: #1651702
changes/53/413553/7
Mark Goddard 6 years ago
parent aa25303e3d
commit 2a80dbb2c9
  1. 10
      dracclient/resources/raid.py
  2. 8
      dracclient/tests/test_raid.py
  3. 87
      dracclient/tests/test_utils.py
  4. 5
      dracclient/tests/wsman_mocks/virtual_disk_view-enum-ok.xml
  5. 39
      dracclient/utils.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

@ -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')

@ -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, [])

@ -30,7 +30,8 @@
<n1:OperationName>Background Intialization</n1:OperationName>
<n1:OperationPercentComplete>8</n1:OperationPercentComplete>
<n1:PendingOperations>0</n1:PendingOperations>
<n1:PhysicalDiskIDs xsi:nil="true"/>
<n1:PhysicalDiskIDs>Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1</n1:PhysicalDiskIDs>
<n1:PhysicalDiskIDs>Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1</n1:PhysicalDiskIDs>
<n1:PrimaryStatus>1</n1:PrimaryStatus>
<n1:RAIDStatus>2</n1:RAIDStatus>
<n1:RAIDTypes>4</n1:RAIDTypes>
@ -51,4 +52,4 @@
<wsman:EndOfSequence/>
</wsen:EnumerateResponse>
</s:Body>
</s:Envelope>
</s:Envelope>

@ -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.

Loading…
Cancel
Save