Fix minor issues in the DRAC driver

This is a following patch of 2eb3c12ce3. The
issues this patch fixes are:

* Put the ReturnValue constants in a common place so it can be used by
  the power and management interfaces code.
* Move the filter object creation to inside the client. This also adds
  a new exception DracInvalidFilterDialect which is raised if when an
  invalid filter dialect is specified.
* Fix the docstring of the find_xml method to reflect the values returned
  in case an element is not found.
* Move the helper test function _mock_wsman_root() to a common place
  and update tests to use it.

Change-Id: Iefc3993f61e32a3a50faf4b9bd80aaab6ae1d3bf
This commit is contained in:
Lucas Alvares Gomes 2014-09-02 17:24:42 +01:00
parent a6caeef289
commit ca2c5a434a
9 changed files with 113 additions and 93 deletions

View File

@ -432,6 +432,11 @@ class DracConfigJobCreationError(DracOperationError):
'Reason: %(error)s')
class DracInvalidFilterDialect(DracOperationError):
message = _('Invalid filter dialect \'%(invalid_filter)s\'. '
'Supported options are %(supported)s')
class FailedToGetSensorData(IronicException):
message = _("Failed to get sensor data for node %(node)s. "
"Error: %(error)s")

View File

@ -24,6 +24,11 @@ pywsman = importutils.try_import('pywsman')
_SOAP_ENVELOPE_URI = 'http://www.w3.org/2003/05/soap-envelope'
# Filter Dialects, see (Section 2.3.1):
# http://en.community.dell.com/techcenter/extras/m/white_papers/20439105.aspx
_FILTER_DIALECT_MAP = {'cql': 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf',
'wql': 'http://schemas.microsoft.com/wbem/wsman/1/WQL'}
class Client(object):
@ -37,19 +42,36 @@ class Client(object):
self.client = pywsman_client
def wsman_enumerate(self, resource_uri, options, filter=None):
def wsman_enumerate(self, resource_uri, options, filter_query=None,
filter_dialect='cql'):
"""Enumerates a remote WS-Man class.
:param resource_uri: URI of the resource.
:param options: client options.
:param filter: filter for enumeration.
:param filter_query: the query string.
:param filter_dialect: the filter dialect. Valid options are:
'cql' and 'wql'. Defaults to 'cql'.
:raises: DracClientError on an error from pywsman library.
:raises: DracInvalidFilterDialect if an invalid filter dialect
was specified.
:returns: an ElementTree object of the response received.
"""
filter_ = None
if filter_query is not None:
try:
filter_dialect = _FILTER_DIALECT_MAP[filter_dialect]
except KeyError:
valid_opts = ', '.join(_FILTER_DIALECT_MAP)
raise exception.DracInvalidFilterDialect(
invalid_filter=filter_dialect, supported=valid_opts)
filter_ = pywsman.Filter()
filter_.simple(filter_dialect, filter_query)
options.set_flags(pywsman.FLAG_ENUMERATION_OPTIMIZATION)
options.set_max_elements(100)
doc = self.client.enumerate(options, filter, resource_uri)
doc = self.client.enumerate(options, filter_, resource_uri)
root = self._get_root(doc)
final_xml = root

View File

@ -37,6 +37,11 @@ OPTIONAL_PROPERTIES = {
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
# ReturnValue constants
RET_SUCCESS = '0'
RET_ERROR = '2'
RET_CREATED = '4096'
def parse_driver_info(node):
"""Parses the driver_info of the node, reads default values
@ -109,8 +114,10 @@ def find_xml(doc, item, namespace, find_all=False):
:param namespace: the namespace of the element.
:param find_all: Boolean value, if True find all elements, if False
find only the first one. Defaults to False.
:returns: The element object if find_all is False or a list of
element objects if find_all is True.
:returns: if find_all is False the element object will be returned
if found, None if not found. If find_all is True a list of
element objects will be returned or an empty list if no
elements were found.
"""
query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace,

View File

@ -50,13 +50,6 @@ NOT_NEXT = '2' # is not the next boot config the system will use
ONE_TIME_BOOT = '3' # is the next boot config the system will use,
# one time boot only
# ReturnValue constants
RET_SUCCESS = '0'
RET_ERROR = '2'
RET_CREATED = '4096'
FILTER_DIALECT = 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf'
def _get_next_boot_mode(node):
"""Get the next boot mode.
@ -75,14 +68,11 @@ def _get_next_boot_mode(node):
"""
client = drac_common.get_wsman_client(node)
options = pywsman.ClientOptions()
filter = pywsman.Filter()
filter_query = ('select * from DCIM_BootConfigSetting where IsNext=%s '
'or IsNext=%s' % (PERSISTENT, ONE_TIME_BOOT))
filter.simple(FILTER_DIALECT, filter_query)
try:
doc = client.wsman_enumerate(resource_uris.DCIM_BootConfigSetting,
options, filter)
options, filter_query=filter_query)
except exception.DracClientError as exc:
with excutils.save_and_reraise_exception():
LOG.error(_LE('DRAC driver failed to get next boot mode for '
@ -138,7 +128,7 @@ def _create_config_job(node):
# or RET_CREATED job created (but changes will be
# applied after the reboot)
# Boot Management Documentation: http://goo.gl/aEsvUH (Section 8.4)
if return_value == RET_ERROR:
if return_value == drac_common.RET_ERROR:
error_message = drac_common.find_xml(doc, 'Message',
resource_uris.DCIM_BIOSService).text
raise exception.DracConfigJobCreationError(error=error_message)
@ -234,15 +224,12 @@ class DracManagement(base.ManagementInterface):
client = drac_common.get_wsman_client(task.node)
options = pywsman.ClientOptions()
filter = pywsman.Filter()
filter_query = ("select * from DCIM_BootSourceSetting where "
"InstanceID like '%%#%s%%'" %
_BOOT_DEVICES_MAP[device])
filter.simple(FILTER_DIALECT, filter_query)
try:
doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting,
options, filter)
options, filter_query=filter_query)
except exception.DracClientError as exc:
with excutils.save_and_reraise_exception():
LOG.error(_LE('DRAC driver failed to set the boot device '
@ -274,7 +261,7 @@ class DracManagement(base.ManagementInterface):
# created (but changes will be applied after
# the reboot)
# Boot Management Documentation: http://goo.gl/aEsvUH (Section 8.7)
if return_value == RET_ERROR:
if return_value == drac_common.RET_ERROR:
error_message = drac_common.find_xml(doc, 'Message',
resource_uris.DCIM_BootConfigSetting).text
raise exception.DracOperationError(operation='set_boot_device',
@ -304,15 +291,12 @@ class DracManagement(base.ManagementInterface):
instance_id = boot_mode['instance_id']
options = pywsman.ClientOptions()
filter = pywsman.Filter()
filter_query = ('select * from DCIM_BootSourceSetting where '
'PendingAssignedSequence=0 and '
'BootSourceType="%s"' % instance_id)
filter.simple(FILTER_DIALECT, filter_query)
try:
doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting,
options, filter)
options, filter_query=filter_query)
except exception.DracClientError as exc:
with excutils.save_and_reraise_exception():
LOG.error(_LE('DRAC driver failed to get the current boot '

View File

@ -51,15 +51,11 @@ def _get_power_state(node):
client = drac_common.get_wsman_client(node)
options = pywsman.ClientOptions()
filter = pywsman.Filter()
filter_dialect = 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf'
filter_query = ('select EnabledState,ElementName from CIM_ComputerSystem '
'where Name="srv:system"')
filter.simple(filter_dialect, filter_query)
try:
doc = client.wsman_enumerate(resource_uris.DCIM_ComputerSystem,
options, filter)
options, filter_query=filter_query)
except exception.DracClientError as exc:
with excutils.save_and_reraise_exception():
LOG.error(_LE('DRAC driver failed to get power state for node '
@ -100,7 +96,7 @@ def _set_power_state(node, target_state):
return_value = drac_common.find_xml(root, 'ReturnValue',
resource_uris.DCIM_ComputerSystem).text
if return_value != '0':
if return_value != drac_common.RET_SUCCESS:
message = drac_common.find_xml(root, 'Message',
resource_uris.DCIM_ComputerSystem).text
LOG.error(_LE('DRAC driver failed to set power state for node '

View File

@ -18,6 +18,7 @@ Test class for DRAC client wrapper.
import mock
from xml.etree import ElementTree
from ironic.common import exception
from ironic.drivers.modules.drac import client as drac_client
from ironic.tests import base
from ironic.tests.db import utils as db_utils
@ -30,12 +31,7 @@ INFO_DICT = db_utils.get_test_drac_info()
class DracClientTestCase(base.TestCase):
def test_wsman_enumerate(self, mock_client_pywsman):
mock_root = mock.Mock()
mock_root.string.return_value = '<test></test>'
mock_xml = mock.Mock()
mock_xml.root.return_value = mock_root
mock_xml.context.return_value = None
mock_xml = test_utils.mock_wsman_root('<test></test>')
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.enumerate.return_value = mock_xml
@ -81,12 +77,33 @@ class DracClientTestCase(base.TestCase):
mock_pywsman_client.enumerate.assert_called_once_with(mock_options,
None, resource_uri)
def test_wsman_invoke(self, mock_client_pywsman):
mock_root = mock.Mock()
mock_root.string.return_value = '<test></test>'
mock_xml = mock.Mock()
mock_xml.root.return_value = mock_root
def test_wsman_enumerate_filter_query(self, mock_client_pywsman):
mock_xml = test_utils.mock_wsman_root('<test></test>')
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.enumerate.return_value = mock_xml
resource_uri = 'https://foo/wsman'
mock_options = mock_client_pywsman.ClientOptions.return_value
mock_filter = mock_client_pywsman.Filter.return_value
client = drac_client.Client(**INFO_DICT)
filter_query = 'SELECT * FROM foo'
client.wsman_enumerate(resource_uri, mock_options,
filter_query=filter_query)
mock_filter.simple.assert_called_once_with(mock.ANY, filter_query)
mock_pywsman_client.enumerate.assert_called_once_with(mock_options,
mock_filter, resource_uri)
mock_xml.context.assert_called_once_with()
def test_wsman_enumerate_invalid_filter_dialect(self, mock_client_pywsman):
client = drac_client.Client(**INFO_DICT)
self.assertRaises(exception.DracInvalidFilterDialect,
client.wsman_enumerate, 'https://foo/wsman',
mock.Mock(), filter_query='foo',
filter_dialect='invalid')
def test_wsman_invoke(self, mock_client_pywsman):
mock_xml = test_utils.mock_wsman_root('<test></test>')
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.invoke.return_value = mock_xml

View File

@ -37,18 +37,6 @@ from ironic.tests.objects import utils as obj_utils
INFO_DICT = db_utils.get_test_drac_info()
def _mock_wsman_root(return_value):
"""Helper function to mock the root() from wsman client."""
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = return_value
mock_xml = mock.Mock()
mock_xml.context.return_value = None
mock_xml.root.return_value = mock_xml_root
return mock_xml
@mock.patch.object(drac_client, 'pywsman')
class DracManagementInternalMethodsTestCase(base.TestCase):
@ -67,7 +55,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase):
drac_mgmt.PERSISTENT}}],
resource_uris.DCIM_BootConfigSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
@ -89,7 +77,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase):
drac_mgmt.ONE_TIME_BOOT}}],
resource_uris.DCIM_BootConfigSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
@ -106,7 +94,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase):
{'Name': 'fake'}}],
resource_uris.DCIM_LifecycleJob)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
@ -123,7 +111,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase):
'InstanceID': 'fake'}}],
resource_uris.DCIM_LifecycleJob)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
@ -134,10 +122,10 @@ class DracManagementInternalMethodsTestCase(base.TestCase):
def test__create_config_job(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_SUCCESS}],
drac_common.RET_SUCCESS}],
resource_uris.DCIM_BIOSService)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.invoke.return_value = mock_xml
@ -149,11 +137,11 @@ class DracManagementInternalMethodsTestCase(base.TestCase):
def test__create_config_job_error(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_ERROR,
drac_common.RET_ERROR,
'Message': 'E_FAKE'}],
resource_uris.DCIM_BIOSService)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.invoke.return_value = mock_xml
@ -194,7 +182,7 @@ class DracManagementTestCase(base.TestCase):
result_xml = test_utils.build_soap_xml([{'InstanceID': 'HardDisk'}],
resource_uris.DCIM_BootSourceSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
@ -213,7 +201,7 @@ class DracManagementTestCase(base.TestCase):
result_xml = test_utils.build_soap_xml([{'InstanceID': 'NIC'}],
resource_uris.DCIM_BootSourceSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
@ -235,7 +223,7 @@ class DracManagementTestCase(base.TestCase):
self.assertRaises(exception.DracClientError,
self.driver.get_boot_device, self.task)
mock_we.assert_called_once_with(resource_uris.DCIM_BootSourceSetting,
mock.ANY, mock.ANY)
mock.ANY, filter_query=mock.ANY)
@mock.patch.object(drac_mgmt, '_check_for_config_job')
@mock.patch.object(drac_mgmt, '_create_config_job')
@ -243,11 +231,11 @@ class DracManagementTestCase(base.TestCase):
result_xml_enum = test_utils.build_soap_xml([{'InstanceID': 'NIC'}],
resource_uris.DCIM_BootSourceSetting)
result_xml_invk = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_SUCCESS}],
drac_common.RET_SUCCESS}],
resource_uris.DCIM_BootConfigSetting)
mock_xml_enum = _mock_wsman_root(result_xml_enum)
mock_xml_invk = _mock_wsman_root(result_xml_invk)
mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum)
mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml_enum
mock_pywsman.invoke.return_value = mock_xml_invk
@ -270,12 +258,12 @@ class DracManagementTestCase(base.TestCase):
result_xml_enum = test_utils.build_soap_xml([{'InstanceID': 'NIC'}],
resource_uris.DCIM_BootSourceSetting)
result_xml_invk = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_ERROR,
drac_common.RET_ERROR,
'Message': 'E_FAKE'}],
resource_uris.DCIM_BootConfigSetting)
mock_xml_enum = _mock_wsman_root(result_xml_enum)
mock_xml_invk = _mock_wsman_root(result_xml_invk)
mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum)
mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml_enum
mock_pywsman.invoke.return_value = mock_xml_invk
@ -302,7 +290,7 @@ class DracManagementTestCase(base.TestCase):
self.driver.set_boot_device, self.task,
boot_devices.PXE)
mock_we.assert_called_once_with(resource_uris.DCIM_BootSourceSetting,
mock.ANY, mock.ANY)
mock.ANY, filter_query=mock.ANY)
def test_get_sensors_data(self, mock_client_pywsman):
self.assertRaises(NotImplementedError,

View File

@ -48,13 +48,7 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
def test__get_power_state(self, mock_power_pywsman, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'EnabledState': '2'}],
resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml
mock_xml = mock.Mock()
mock_xml.context.return_value = None
mock_xml.root.return_value = mock_xml_root
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.enumerate.return_value = mock_xml
@ -65,14 +59,10 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
mock.ANY, resource_uris.DCIM_ComputerSystem)
def test__set_power_state(self, mock_power_pywsman, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
result_xml = test_utils.build_soap_xml([{'ReturnValue':
drac_common.RET_SUCCESS}],
resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml
mock_xml = mock.Mock()
mock_xml.root.return_value = mock_xml_root
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.invoke.return_value = mock_xml
@ -92,15 +82,12 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
def test__set_power_state_fail(self, mock_power_pywsman,
mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'ReturnValue': '1',
result_xml = test_utils.build_soap_xml([{'ReturnValue':
drac_common.RET_ERROR,
'Message': 'error message'}],
resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml
mock_xml = mock.Mock()
mock_xml.root.return_value = mock_xml_root
mock_xml = test_utils.mock_wsman_root(result_xml)
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.invoke.return_value = mock_xml

View File

@ -17,6 +17,8 @@
from xml.etree import ElementTree
import mock
def build_soap_xml(items, namespace=None):
"""Build a SOAP XML.
@ -56,3 +58,15 @@ def build_soap_xml(items, namespace=None):
envelope_element.append(body_element)
return ElementTree.tostring(envelope_element)
def mock_wsman_root(return_value):
"""Helper function to mock the root() from wsman client."""
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = return_value
mock_xml = mock.Mock()
mock_xml.context.return_value = None
mock_xml.root.return_value = mock_xml_root
return mock_xml