diff --git a/ironic/common/exception.py b/ironic/common/exception.py
index 765ae1087c..edd0da32e4 100644
--- a/ironic/common/exception.py
+++ b/ironic/common/exception.py
@@ -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")
diff --git a/ironic/drivers/modules/drac/client.py b/ironic/drivers/modules/drac/client.py
index 99b052db6e..73cfe13cf9 100644
--- a/ironic/drivers/modules/drac/client.py
+++ b/ironic/drivers/modules/drac/client.py
@@ -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
diff --git a/ironic/drivers/modules/drac/common.py b/ironic/drivers/modules/drac/common.py
index 9413ed078c..dddf68bbc1 100644
--- a/ironic/drivers/modules/drac/common.py
+++ b/ironic/drivers/modules/drac/common.py
@@ -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,
diff --git a/ironic/drivers/modules/drac/management.py b/ironic/drivers/modules/drac/management.py
index ede5bf4df4..4598f078b5 100644
--- a/ironic/drivers/modules/drac/management.py
+++ b/ironic/drivers/modules/drac/management.py
@@ -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 '
diff --git a/ironic/drivers/modules/drac/power.py b/ironic/drivers/modules/drac/power.py
index 2f48aff9c2..b532817247 100644
--- a/ironic/drivers/modules/drac/power.py
+++ b/ironic/drivers/modules/drac/power.py
@@ -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 '
diff --git a/ironic/tests/drivers/drac/test_client.py b/ironic/tests/drivers/drac/test_client.py
index b4c1ac83bd..e6d8e165ee 100644
--- a/ironic/tests/drivers/drac/test_client.py
+++ b/ironic/tests/drivers/drac/test_client.py
@@ -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 = ''
- mock_xml = mock.Mock()
- mock_xml.root.return_value = mock_root
- mock_xml.context.return_value = None
-
+ mock_xml = test_utils.mock_wsman_root('')
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 = ''
- 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('')
+ 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('')
mock_pywsman_client = mock_client_pywsman.Client.return_value
mock_pywsman_client.invoke.return_value = mock_xml
diff --git a/ironic/tests/drivers/drac/test_management.py b/ironic/tests/drivers/drac/test_management.py
index e5a7ef1f84..a45214465e 100644
--- a/ironic/tests/drivers/drac/test_management.py
+++ b/ironic/tests/drivers/drac/test_management.py
@@ -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,
diff --git a/ironic/tests/drivers/drac/test_power.py b/ironic/tests/drivers/drac/test_power.py
index 9d97cf0844..7b8699a42d 100644
--- a/ironic/tests/drivers/drac/test_power.py
+++ b/ironic/tests/drivers/drac/test_power.py
@@ -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
diff --git a/ironic/tests/drivers/drac/utils.py b/ironic/tests/drivers/drac/utils.py
index 2d2100dd38..c31bc28071 100644
--- a/ironic/tests/drivers/drac/utils.py
+++ b/ironic/tests/drivers/drac/utils.py
@@ -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