diff --git a/ironic/drivers/drac.py b/ironic/drivers/drac.py
index a74450d942..4f6b6dccee 100644
--- a/ironic/drivers/drac.py
+++ b/ironic/drivers/drac.py
@@ -21,8 +21,10 @@ from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules.drac import management
from ironic.drivers.modules.drac import power
+from ironic.drivers.modules.drac import vendor_passthru
from ironic.drivers.modules import inspector
from ironic.drivers.modules import pxe
+from ironic.drivers import utils
class PXEDracDriver(base.BaseDriver):
@@ -37,6 +39,16 @@ class PXEDracDriver(base.BaseDriver):
self.power = power.DracPower()
self.deploy = pxe.PXEDeploy()
self.management = management.DracManagement()
- self.vendor = pxe.VendorPassthru()
+ self.pxe_vendor = pxe.VendorPassthru()
+ self.drac_vendor = vendor_passthru.DracVendorPassthru()
+ self.mapping = {'pass_deploy_info': self.pxe_vendor,
+ 'heartbeat': self.pxe_vendor,
+ 'pass_bootloader_install_info': self.pxe_vendor,
+ 'get_bios_config': self.drac_vendor,
+ 'set_bios_config': self.drac_vendor,
+ 'commit_bios_config': self.drac_vendor,
+ 'abandon_bios_config': self.drac_vendor,
+ }
+ self.vendor = utils.MixinVendorInterface(self.mapping)
self.inspect = inspector.Inspector.create_if_enabled(
'PXEDracDriver')
diff --git a/ironic/drivers/modules/drac/bios.py b/ironic/drivers/modules/drac/bios.py
new file mode 100644
index 0000000000..a74f01e998
--- /dev/null
+++ b/ironic/drivers/modules/drac/bios.py
@@ -0,0 +1,430 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+DRAC Bios specific methods
+"""
+
+import re
+from xml.etree import ElementTree as ET
+
+from oslo_log import log as logging
+from oslo_utils import excutils
+
+from ironic.common import exception
+from ironic.common.i18n import _, _LE, _LW
+from ironic.conductor import task_manager
+from ironic.drivers.modules.drac import client as wsman_client
+from ironic.drivers.modules.drac import management
+from ironic.drivers.modules.drac import resource_uris
+
+LOG = logging.getLogger(__name__)
+
+
+def _val_or_none(item):
+ """Test to see if an XML element should be treated as None.
+
+ If the element contains an XML Schema namespaced nil attribute that
+ has a value of True, return None. Otherwise, return whatever the
+ text of the element is.
+
+ :param item: an XML element.
+ :returns: None or the test of the XML element.
+ """
+
+ if item is None:
+ return
+ itemnil = item.attrib.get('{%s}nil' % resource_uris.CIM_XmlSchema)
+ if itemnil == "true":
+ return
+ else:
+ return item.text
+
+
+def _parse_common(item, ns):
+ """Parse common values that all attributes must have.
+
+ :param item: an XML element.
+ :param ns: the namespace to search.
+ :returns: a dictionary containing the parsed attributes of the element.
+ :raises: DracOperationFailed if the given element had no AttributeName
+ value.
+ """
+ searches = {'current_value': './{%s}CurrentValue' % ns,
+ 'read_only': './{%s}IsReadOnly' % ns,
+ 'pending_value': './{%s}PendingValue' % ns}
+ LOG.debug("Handing %(ns)s for %(xml)s", {
+ 'ns': ns,
+ 'xml': ET.tostring(item),
+ })
+ name = item.findtext('./{%s}AttributeName' % ns)
+ if not name:
+ raise exception.DracOperationFailed(
+ message=_('Item has no name: "%s"') % ET.tostring(item))
+ res = {}
+ res['name'] = name
+
+ for k in searches:
+ if k == 'read_only':
+ res[k] = item.findtext(searches[k]) == 'true'
+ else:
+ res[k] = _val_or_none(item.find(searches[k]))
+ return res
+
+
+def _format_error_msg(invalid_attribs_msgs, read_only_keys):
+ """Format a combined error message.
+
+ This method creates a combined error message from a list of error messages
+ and a list of read-only keys.
+
+ :param invalid_attribs_msgs: a list of invalid attribute error messages.
+ :param read_only_keys: a list of read only keys that were attempted to be
+ written to.
+ :returns: a formatted error message.
+ """
+ msg = '\n'.join(invalid_attribs_msgs)
+ if invalid_attribs_msgs and read_only_keys:
+ msg += '\n'
+ if read_only_keys:
+ msg += (_('Cannot set read-only BIOS settings "%r"') % read_only_keys)
+ return msg
+
+
+def parse_enumeration(item, ns):
+ """Parse an attribute that has a set of distinct values.
+
+ :param item: an XML element.
+ :param ns: the namespace to search.
+ :returns: a dictionary containing the parsed attributes of the element.
+ :raises: DracOperationFailed if the given element had no AttributeName
+ value.
+ """
+ res = _parse_common(item, ns)
+ res['possible_values'] = sorted(
+ [v.text for v in item.findall('./{%s}PossibleValues' % ns)])
+
+ return res
+
+
+def parse_string(item, ns):
+ """Parse an attribute that should be a freeform string.
+
+ :param item: an XML element.
+ :param ns: the namespace to search.
+ :returns: a dictionary containing the parsed attributes of the element.
+ :raises: DracOperationFailed if the given element had no AttributeName
+ value.
+ """
+ res = _parse_common(item, ns)
+ searches = {'min_length': './{%s}MinLength' % ns,
+ 'max_length': './{%s}MaxLength' % ns,
+ 'pcre_regex': './{%s}ValueExpression' % ns}
+ for k in searches:
+ if k == 'pcre_regex':
+ res[k] = _val_or_none(item.find(searches[k]))
+ else:
+ res[k] = int(item.findtext(searches[k]))
+
+ # Workaround for a BIOS bug in one of the 13 gen boxes
+ badval = re.compile(r"MAX_ASSET_TAG_LEN")
+ if (res['pcre_regex'] is not None and
+ res['name'] == 'AssetTag' and
+ badval.search(res['pcre_regex'])):
+ res['pcre_regex'] = badval.sub("%d" % res['max_length'],
+ res['pcre_regex'])
+ return res
+
+
+def parse_integer(item, ns):
+ """Parse an attribute that should be an integer.
+
+ :param item: an XML element.
+ :param ns: the namespace to search.
+ :returns: a dictionary containing the parsed attributes of the element.
+ :raises: DracOperationFailed if the given element had no AttributeName
+ value.
+ """
+ res = _parse_common(item, ns)
+ for k in ['current_value', 'pending_value']:
+ if res[k]:
+ res[k] = int(res[k])
+ searches = {'lower_bound': './{%s}LowerBound' % ns,
+ 'upper_bound': './{%s}UpperBound' % ns}
+ for k in searches:
+ res[k] = int(item.findtext(searches[k]))
+
+ return res
+
+
+def _get_config(node, resource):
+ """Helper for get_config.
+
+ Handles getting BIOS config values for a single namespace
+
+ :param node: an ironic node object.
+ :param resource: the namespace.
+ :returns: a dictionary that maps the name of each attribute to a dictionary
+ of values of that attribute.
+ :raises: InvalidParameterValue if some information required to connnect
+ to the DRAC is missing on the node or the value of one or more
+ required parameters is invalid.
+ :raises: DracClientError on an error from pywsman library.
+ :raises: DracOperationFailed if the specified resource is unknown.
+ """
+ res = {}
+ client = wsman_client.get_wsman_client(node)
+ try:
+ doc = client.wsman_enumerate(resource)
+ except exception.DracClientError as exc:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('DRAC driver failed to get BIOS settings '
+ 'for resource %(resource)s '
+ 'from node %(node_uuid)s. '
+ 'Reason: %(error)s.'),
+ {'node_uuid': node.uuid,
+ 'resource': resource,
+ 'error': exc})
+ items = doc.find('.//{%s}Items' % resource_uris.CIM_WSMAN)
+ for item in items:
+ if resource == resource_uris.DCIM_BIOSEnumeration:
+ attribute = parse_enumeration(item, resource)
+ elif resource == resource_uris.DCIM_BIOSString:
+ attribute = parse_string(item, resource)
+ elif resource == resource_uris.DCIM_BIOSInteger:
+ attribute = parse_integer(item, resource)
+ else:
+ raise exception.DracOperationFailed(
+ message=_('Unknown namespace %(ns)s for item: "%(item)s"') % {
+ 'item': ET.tostring(item), 'ns': resource})
+ res[attribute['name']] = attribute
+ return res
+
+
+def get_config(node):
+ """Get the BIOS configuration from a Dell server using WSMAN
+
+ :param node: an ironic node object.
+ :raises: DracClientError on an error from pywsman.
+ :raises: DracOperationFailed when a BIOS setting cannot be parsed.
+ :returns: a dictionary containing BIOS settings in the form of:
+ {'EnumAttrib': {'name': 'EnumAttrib',
+ 'current_value': 'Value',
+ 'pending_value': 'New Value', # could also be None
+ 'read_only': False,
+ 'possible_values': ['Value', 'New Value', 'None']},
+ 'StringAttrib': {'name': 'StringAttrib',
+ 'current_value': 'Information',
+ 'pending_value': None,
+ 'read_only': False,
+ 'min_length': 0,
+ 'max_length': 255,
+ 'pcre_regex': '^[0-9A-Za-z]{0,255}$'},
+ 'IntegerAttrib': {'name': 'IntegerAttrib',
+ 'current_value': 0,
+ 'pending_value': None,
+ 'read_only': True,
+ 'lower_bound': 0,
+ 'upper_bound': 65535}
+ }
+
+ The above values are only examples, of course. BIOS attributes exposed via
+ this API will always be either an enumerated attribute, a string attribute,
+ or an integer attribute. All attributes have the following parameters:
+ :name: is the name of the BIOS attribute.
+ :current_value: is the current value of the attribute.
+ It will always be either an integer or a string.
+ :pending_value: is the new value that we want the attribute to have.
+ None means that there is no pending value.
+ :read_only: indicates whether this attribute can be changed. Trying to
+ change a read-only value will result in an error.
+ The read-only flag can change depending on other attributes.
+ A future version of this call may expose the dependencies
+ that indicate when that may happen.
+
+ Enumerable attributes also have the following parameters:
+ :possible_values: is an array of values it is permissible to set
+ the attribute to.
+
+ String attributes also have the following parameters:
+ :min_length: is the minimum length of the string.
+ :max_length: is the maximum length of the string.
+ :pcre_regex: is a PCRE compatible regular expression that the string
+ must match. It may be None if the string is read only
+ or if the string does not have to match any particular
+ regular expression.
+
+ Integer attributes also have the following parameters:
+ :lower_bound: is the minimum value the attribute can have.
+ :upper_bound: is the maximum value the attribute can have.
+
+ """
+ res = {}
+ for ns in [resource_uris.DCIM_BIOSEnumeration,
+ resource_uris.DCIM_BIOSString,
+ resource_uris.DCIM_BIOSInteger]:
+ attribs = _get_config(node, ns)
+ if not set(res).isdisjoint(set(attribs)):
+ raise exception.DracOperationFailed(
+ message=_('Colliding attributes %r') % (
+ set(res) & set(attribs)))
+ res.update(attribs)
+ return res
+
+
+@task_manager.require_exclusive_lock
+def set_config(task, **kwargs):
+ """Sets the pending_value parameter for each of the values passed in.
+
+ :param task: an ironic task object.
+ :param kwargs: a dictionary of {'AttributeName': 'NewValue'}
+ :raises: DracOperationFailed if any new values are invalid.
+ :raises: DracOperationFailed if any of the attributes are read-only.
+ :raises: DracOperationFailed if any of the attributes cannot be set for
+ any other reason.
+ :raises: DracClientError on an error from the pywsman library.
+ :returns: A boolean indicating whether commit_config needs to be
+ called to make the changes.
+
+ """
+ node = task.node
+ management.check_for_config_job(node)
+ current = get_config(node)
+ unknown_keys = set(kwargs) - set(current)
+ if unknown_keys:
+ LOG.warning(_LW('Ignoring unknown BIOS attributes "%r"'),
+ unknown_keys)
+
+ candidates = set(kwargs) - unknown_keys
+ read_only_keys = []
+ unchanged_attribs = []
+ invalid_attribs_msgs = []
+ attrib_names = []
+
+ for k in candidates:
+ if str(kwargs[k]) == str(current[k]['current_value']):
+ unchanged_attribs.append(k)
+ elif current[k]['read_only']:
+ read_only_keys.append(k)
+ else:
+ if 'possible_values' in current[k]:
+ if str(kwargs[k]) not in current[k]['possible_values']:
+ m = _('Attribute %(attr)s cannot be set to value %(val)s.'
+ ' It must be in %(ok)r') % {
+ 'attr': k,
+ 'val': kwargs[k],
+ 'ok': current[k]['possible_values']}
+ invalid_attribs_msgs.append(m)
+ continue
+ if ('pcre_regex' in current[k] and
+ current[k]['pcre_regex'] is not None):
+ regex = re.compile(current[k]['pcre_regex'])
+ if regex.search(str(kwargs[k])) is None:
+ # TODO(victor-lowther)
+ # Leave untranslated for now until the unicode
+ # issues that the test suite exposes are straightened out.
+ m = ('Attribute %(attr)s cannot be set to value %(val)s.'
+ ' It must match regex %(re)s.') % {
+ 'attr': k,
+ 'val': kwargs[k],
+ 're': current[k]['pcre_regex']}
+ invalid_attribs_msgs.append(m)
+ continue
+ if 'lower_bound' in current[k]:
+ lower = current[k]['lower_bound']
+ upper = current[k]['upper_bound']
+ val = int(kwargs[k])
+ if val < lower or val > upper:
+ m = _('Attribute %(attr)s cannot be set to value %(val)d.'
+ ' It must be between %(lower)d and %(upper)d.') % {
+ 'attr': k,
+ 'val': val,
+ 'lower': lower,
+ 'upper': upper}
+ invalid_attribs_msgs.append(m)
+ continue
+ attrib_names.append(k)
+
+ if unchanged_attribs:
+ LOG.warn(_LW('Ignoring unchanged BIOS settings %r'),
+ unchanged_attribs)
+
+ if invalid_attribs_msgs or read_only_keys:
+ raise exception.DracOperationFailed(
+ _format_error_msg(invalid_attribs_msgs, read_only_keys))
+
+ if not attrib_names:
+ return False
+
+ client = wsman_client.get_wsman_client(node)
+ selectors = {'CreationClassName': 'DCIM_BIOSService',
+ 'Name': 'DCIM:BIOSService',
+ 'SystemCreationClassName': 'DCIM_ComputerSystem',
+ 'SystemName': 'DCIM:ComputerSystem'}
+ properties = {'Target': 'BIOS.Setup.1-1',
+ 'AttributeName': attrib_names,
+ 'AttributeValue': map(lambda k: kwargs[k], attrib_names)}
+ doc = client.wsman_invoke(resource_uris.DCIM_BIOSService,
+ 'SetAttributes',
+ selectors,
+ properties)
+ # Yes, we look for RebootRequired. In this context, that actually means
+ # that we need to create a lifecycle controller config job and then reboot
+ # so that the lifecycle controller can commit the BIOS config changes that
+ # we have proposed.
+ set_results = doc.findall(
+ './/{%s}RebootRequired' % resource_uris.DCIM_BIOSService)
+ return any(str(res.text) == 'Yes' for res in set_results)
+
+
+@task_manager.require_exclusive_lock
+def commit_config(task):
+ """Commits pending changes added by set_config
+
+ :param task: is the ironic task for running the config job.
+ :raises: DracClientError on an error from pywsman library.
+ :raises: DracPendingConfigJobExists if the job is already created.
+ :raises: DracOperationFailed if the client received response with an
+ error message.
+ :raises: DracUnexpectedReturnValue if the client received a response
+ with unexpected return value
+
+ """
+ node = task.node
+ management.check_for_config_job(node)
+ management.create_config_job(node)
+
+
+@task_manager.require_exclusive_lock
+def abandon_config(task):
+ """Abandons uncommitted changes added by set_config
+
+ :param task: is the ironic task for abandoning the changes.
+ :raises: DracClientError on an error from pywsman library.
+ :raises: DracOperationFailed on error reported back by DRAC.
+ :raises: DracUnexpectedReturnValue if the drac did not report success.
+
+ """
+ node = task.node
+ client = wsman_client.get_wsman_client(node)
+ selectors = {'CreationClassName': 'DCIM_BIOSService',
+ 'Name': 'DCIM:BIOSService',
+ 'SystemCreationClassName': 'DCIM_ComputerSystem',
+ 'SystemName': 'DCIM:ComputerSystem'}
+ properties = {'Target': 'BIOS.Setup.1-1'}
+
+ client.wsman_invoke(resource_uris.DCIM_BIOSService,
+ 'DeletePendingConfiguration',
+ selectors,
+ properties,
+ wsman_client.RET_SUCCESS)
diff --git a/ironic/drivers/modules/drac/client.py b/ironic/drivers/modules/drac/client.py
index 5b8b4b9411..58c62c59dc 100644
--- a/ironic/drivers/modules/drac/client.py
+++ b/ironic/drivers/modules/drac/client.py
@@ -147,6 +147,8 @@ class Client(object):
doc = retry_on_empty_response(self.client, 'enumerate',
options, filter_, resource_uri)
root = self._get_root(doc)
+ LOG.debug("WSMAN enumerate returned raw XML: %s",
+ ElementTree.tostring(root))
final_xml = root
find_query = './/{%s}Body' % _SOAP_ENVELOPE_URI
@@ -155,6 +157,9 @@ class Client(object):
doc = retry_on_empty_response(self.client, 'pull', options, None,
resource_uri, str(doc.context()))
root = self._get_root(doc)
+ LOG.debug("WSMAN pull returned raw XML: %s",
+ ElementTree.tostring(root))
+
for result in root.findall(find_query):
for child in list(result):
insertion_point.append(child)
@@ -162,14 +167,14 @@ class Client(object):
return final_xml
def wsman_invoke(self, resource_uri, method, selectors=None,
- properties=None, expected_return_value=RET_SUCCESS):
+ properties=None, expected_return=None):
"""Invokes a remote WS-Man method.
:param resource_uri: URI of the resource.
:param method: name of the method to invoke.
:param selectors: dictionary of selectors.
:param properties: dictionary of properties.
- :param expected_return_value: expected return value.
+ :param expected_return: expected return value.
:raises: DracClientError on an error from pywsman library.
:raises: DracOperationFailed on error reported back by DRAC.
:raises: DracUnexpectedReturnValue on return value mismatch.
@@ -199,9 +204,16 @@ class Client(object):
for name, value in properties.items():
if isinstance(value, list):
for item in value:
- xml_root.add(resource_uri, name, item)
+ xml_root.add(resource_uri, str(name), str(item))
else:
xml_root.add(resource_uri, name, value)
+ LOG.debug(('WSMAN invoking: %(resource_uri)s:%(method)s'
+ '\nselectors: %(selectors)r\nxml: %(xml)s'),
+ {
+ 'resource_uri': resource_uri,
+ 'method': method,
+ 'selectors': selectors,
+ 'xml': xml_root.string()})
else:
xml_doc = None
@@ -209,22 +221,39 @@ class Client(object):
for name, value in properties.items():
options.add_property(name, value)
+ LOG.debug(('WSMAN invoking: %(resource_uri)s:%(method)s'
+ '\nselectors: %(selectors)r\properties: %(props)r') % {
+ 'resource_uri': resource_uri,
+ 'method': method,
+ 'selectors': selectors,
+ 'props': properties})
+
doc = retry_on_empty_response(self.client, 'invoke', options,
resource_uri, method, xml_doc)
-
root = self._get_root(doc)
+ LOG.debug("WSMAN invoke returned raw XML: %s",
+ ElementTree.tostring(root))
return_value = drac_common.find_xml(root, 'ReturnValue',
resource_uri).text
- if return_value != expected_return_value:
- if return_value == RET_ERROR:
- message = drac_common.find_xml(root, 'Message',
- resource_uri).text
- raise exception.DracOperationFailed(message=message)
+ if return_value == RET_ERROR:
+ messages = drac_common.find_xml(root, 'Message',
+ resource_uri, True)
+ message_args = drac_common.find_xml(root, 'MessageArguments',
+ resource_uri, True)
+
+ if message_args:
+ messages = [m.text % p.text for (m, p) in
+ zip(messages, message_args)]
else:
- raise exception.DracUnexpectedReturnValue(
- expected_return_value=expected_return_value,
- actual_return_value=return_value)
+ messages = [m.text for m in messages]
+
+ raise exception.DracOperationFailed(message='%r' % messages)
+
+ if expected_return and return_value != expected_return:
+ raise exception.DracUnexpectedReturnValue(
+ expected_return_value=expected_return,
+ actual_return_value=return_value)
return root
diff --git a/ironic/drivers/modules/drac/management.py b/ironic/drivers/modules/drac/management.py
index e1dad97386..e69a435733 100644
--- a/ironic/drivers/modules/drac/management.py
+++ b/ironic/drivers/modules/drac/management.py
@@ -221,7 +221,7 @@ def _get_boot_list_for_boot_device(node, device, controller_version):
return {'boot_list': boot_list, 'boot_device_id': boot_device_id}
-def _create_config_job(node):
+def create_config_job(node):
"""Create a configuration job.
This method is used to apply the pending values created by
@@ -253,7 +253,7 @@ def _create_config_job(node):
{'node_uuid': node.uuid, 'error': exc})
-def _check_for_config_job(node):
+def check_for_config_job(node):
"""Check if a configuration job is already created.
:param node: an ironic node object.
@@ -379,7 +379,7 @@ class DracManagement(base.ManagementInterface):
return
# Check for an existing configuration job
- _check_for_config_job(task.node)
+ check_for_config_job(task.node)
# Querying the boot device attributes
boot_device = _get_boot_list_for_boot_device(task.node, device,
@@ -396,7 +396,7 @@ class DracManagement(base.ManagementInterface):
try:
client.wsman_invoke(resource_uris.DCIM_BootConfigSetting,
'ChangeBootOrderByInstanceID', selectors,
- properties)
+ properties, drac_client.RET_SUCCESS)
except exception.DracRequestFailed as exc:
with excutils.save_and_reraise_exception():
LOG.error(_LE('DRAC driver failed to set the boot device for '
@@ -407,7 +407,7 @@ class DracManagement(base.ManagementInterface):
'error': exc})
# Create a configuration job
- _create_config_job(task.node)
+ create_config_job(task.node)
def get_boot_device(self, task):
"""Get the current boot device for a node.
diff --git a/ironic/drivers/modules/drac/resource_uris.py b/ironic/drivers/modules/drac/resource_uris.py
index 03a5385cb0..d8e2369c09 100644
--- a/ironic/drivers/modules/drac/resource_uris.py
+++ b/ironic/drivers/modules/drac/resource_uris.py
@@ -28,8 +28,19 @@ DCIM_BootConfigSetting = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
DCIM_BIOSService = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
'DCIM_BIOSService')
+DCIM_BIOSEnumeration = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
+ 'DCIM_BIOSEnumeration')
+DCIM_BIOSString = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
+ 'DCIM_BIOSString')
+DCIM_BIOSInteger = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
+ 'DCIM_BIOSInteger')
+
DCIM_LifecycleJob = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
'DCIM_LifecycleJob')
DCIM_SystemView = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
'DCIM_SystemView')
+
+CIM_XmlSchema = 'http://www.w3.org/2001/XMLSchema-instance'
+
+CIM_WSMAN = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
diff --git a/ironic/drivers/modules/drac/vendor_passthru.py b/ironic/drivers/modules/drac/vendor_passthru.py
new file mode 100644
index 0000000000..0be681f2d8
--- /dev/null
+++ b/ironic/drivers/modules/drac/vendor_passthru.py
@@ -0,0 +1,118 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+DRAC VendorPassthruBios Driver
+"""
+
+from ironic.drivers import base
+from ironic.drivers.modules.drac import bios
+from ironic.drivers.modules.drac import common as drac_common
+
+
+class DracVendorPassthru(base.VendorInterface):
+ """Interface for DRAC specific BIOS configuration methods."""
+
+ def get_properties(self):
+ """Returns the driver_info properties.
+
+ This method returns the driver_info properties for this driver.
+
+ :returns: a dictionary of propery names and their descriptions.
+ """
+ return drac_common.COMMON_PROPERTIES
+
+ def validate(self, task, **kwargs):
+ """Validates the driver_info of a node.
+
+ This method validates the driver_info associated with the node that is
+ associated with the task.
+
+ :param task: the ironic task used to identify the node.
+ :param kwargs: not used.
+ :raises: InvalidParameterValue if mandatory information is missing on
+ the node or any driver_info is invalid.
+ :returns: a dict containing information from driver_info
+ and default values.
+ """
+ return drac_common.parse_driver_info(task.node)
+
+ @base.passthru(['GET'], async=False)
+ def get_bios_config(self, task, **kwargs):
+ """Get BIOS settings.
+
+ This method is used to retrieve the BIOS settings from a node.
+
+ :param task: the ironic task used to identify the node.
+ :param kwargs: not used.
+ :raises: DracClientError on an error from pywsman.
+ :raises: DracOperationFailed when a BIOS setting cannot be parsed.
+ :returns: a dictionary containing BIOS settings.
+ """
+ return bios.get_config(task.node)
+
+ @base.passthru(['POST'], async=False)
+ def set_bios_config(self, task, **kwargs):
+ """Change BIOS settings.
+
+ This method is used to change the BIOS settings on a node.
+
+ :param task: the ironic task used to identify the node.
+ :param kwargs: a dictionary of {'AttributeName': 'NewValue'}
+ :raises: DracOperationFailed if any of the attributes cannot be set for
+ any reason.
+ :raises: DracClientError on an error from the pywsman library.
+ :returns: A dictionary containing the commit_needed key with a boolean
+ value indicating whether commit_config() needs to be called
+ to make the changes.
+ """
+ return {'commit_needed': bios.set_config(task, **kwargs)}
+
+ @base.passthru(['POST'], async=False)
+ def commit_bios_config(self, task, **kwargs):
+ """Commit a BIOS configuration job.
+
+ This method is used to commit a BIOS configuration job.
+ submitted through set_bios_config().
+
+ :param task: the ironic task for running the config job.
+ :param kwargs: not used.
+ :raises: DracClientError on an error from pywsman library.
+ :raises: DracPendingConfigJobExists if the job is already created.
+ :raises: DracOperationFailed if the client received response with an
+ error message.
+ :raises: DracUnexpectedReturnValue if the client received a response
+ with unexpected return value
+ :returns: A dictionary containing the committing key with no return
+ value, and the reboot_needed key with a value of True.
+ """
+ bios.commit_config(task)
+ return {'committing': None, 'reboot_needed': True}
+
+ @base.passthru(['DELETE'], async=False)
+ def abandon_bios_config(self, task, **kwargs):
+ """Abandon a BIOS configuration job.
+
+ This method is used to abandon a BIOS configuration job previously
+ submitted through set_bios_config().
+
+ :param task: the ironic task for abandoning the changes.
+ :param kwargs: not used.
+ :raises: DracClientError on an error from pywsman library.
+ :raises: DracOperationFailed on error reported back by DRAC.
+ :raises: DracUnexpectedReturnValue if the drac did not report success.
+ :returns: A dictionary containing the abandoned key with no return
+ value.
+ """
+ bios.abandon_config(task)
+ return {'abandoned': None}
diff --git a/ironic/tests/drivers/drac/bios_wsman_mock.py b/ironic/tests/drivers/drac/bios_wsman_mock.py
new file mode 100644
index 0000000000..245d27c016
--- /dev/null
+++ b/ironic/tests/drivers/drac/bios_wsman_mock.py
@@ -0,0 +1,273 @@
+#
+# Copyright 2015 Dell, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Test class for DRAC BIOS interface
+"""
+
+from ironic.drivers.modules.drac import resource_uris
+
+Enumerations = {
+ resource_uris.DCIM_BIOSEnumeration: {
+ 'XML': """
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+
+http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse
+ uuid:1f5cd907-0e6f-1e6f-8002-4f266e3acab8
+ uuid:219ca357-0e6f-1e6f-a828-f0e4fb722ab8
+
+
+
+
+
+ MemTest
+ Disabled
+
+ 310
+ BIOS.Setup.1-1
+ Memory Settings
+ MemSettings
+ BIOS.Setup.1-1:MemTest
+ false
+
+ Enabled
+ Disabled
+
+
+ C States
+ ProcCStates
+ Disabled
+ 1706
+ BIOS.Setup.1-1
+ System Profile Settings
+ SysProfileSettings
+ BIOS.Setup.1-1:ProcCStates
+ true
+
+ Enabled
+ Disabled
+
+
+
+
+ """,
+ 'Dict': {
+ 'MemTest': {
+ 'name': 'MemTest',
+ 'current_value': 'Disabled',
+ 'pending_value': None,
+ 'read_only': False,
+ 'possible_values': ['Disabled', 'Enabled']},
+ 'ProcCStates': {
+ 'name': 'ProcCStates',
+ 'current_value': 'Disabled',
+ 'pending_value': None,
+ 'read_only': True,
+ 'possible_values': ['Disabled', 'Enabled']}}},
+ resource_uris.DCIM_BIOSString: {
+ 'XML': """
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+
+http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse
+
+ uuid:1f877bcb-0e6f-1e6f-8004-4f266e3acab8
+ uuid:21bea321-0e6f-1e6f-a82b-f0e4fb722ab8
+
+
+
+
+
+ SystemModelName
+ PowerEdge R630
+
+ 201
+ BIOS.Setup.1-1
+ System Information
+ SysInformation
+ BIOS.Setup.1-1:SystemModelName
+ true
+ 40
+ 0
+
+
+
+
+ SystemModelName2
+ PowerEdge R630
+
+ 201
+ BIOS.Setup.1-1
+ System Information
+ SysInformation
+ BIOS.Setup.1-1:SystemModelName2
+ true
+ 40
+ 0
+
+
+
+ Asset Tag
+ AssetTag
+
+
+ 1903
+ BIOS.Setup.1-1
+ Miscellaneous Settings
+ MiscSettings
+ BIOS.Setup.1-1:AssetTag
+ false
+ 63
+ 0
+
+ ^[ -~]{0,63}$
+
+
+
+
+
+
+ """,
+ 'Dict': {
+ 'SystemModelName': {
+ 'name': 'SystemModelName',
+ 'current_value': 'PowerEdge R630',
+ 'pending_value': None,
+ 'read_only': True,
+ 'min_length': 0,
+ 'max_length': 40,
+ 'pcre_regex': None},
+ 'SystemModelName2': {
+ 'name': 'SystemModelName2',
+ 'current_value': 'PowerEdge R630',
+ 'pending_value': None,
+ 'read_only': True,
+ 'min_length': 0,
+ 'max_length': 40,
+ 'pcre_regex': None},
+ 'AssetTag': {
+ 'name': 'AssetTag',
+ 'current_value': None,
+ 'pending_value': None,
+ 'read_only': False,
+ 'min_length': 0,
+ 'max_length': 63,
+ 'pcre_regex': '^[ -~]{0,63}$'}}},
+ resource_uris.DCIM_BIOSInteger: {
+ 'XML': """
+
+
+http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse
+ uuid:1fa60792-0e6f-1e6f-8005-4f266e3acab8
+ uuid:21ccf01d-0e6f-1e6f-a82d-f0e4fb722ab8
+
+
+
+
+
+ Proc1NumCores
+ 8
+
+ 439
+ BIOS.Setup.1-1
+ Processor Settings
+ ProcSettings
+ BIOS.Setup.1-1:Proc1NumCores
+ true
+ 0
+
+ 65535
+
+
+ AcPwrRcvryUserDelay
+ 60
+ 1825
+ BIOS.Setup.1-1
+ System Security
+ SysSecurity
+ BIOS.Setup.1-1:AcPwrRcvryUserDelay
+ false
+ 60
+
+ 240
+
+
+
+
+
+
+ """,
+ 'Dict': {
+ 'Proc1NumCores': {
+ 'name': 'Proc1NumCores',
+ 'current_value': 8,
+ 'pending_value': None,
+ 'read_only': True,
+ 'lower_bound': 0,
+ 'upper_bound': 65535},
+ 'AcPwrRcvryUserDelay': {
+ 'name': 'AcPwrRcvryUserDelay',
+ 'current_value': 60,
+ 'pending_value': None,
+ 'read_only': False,
+ 'lower_bound': 60,
+ 'upper_bound': 240}}}}
+
+Invoke_Commit = """
+
+
+http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/SetAttributesResponse
+ uuid:42baa476-0ee9-1ee9-8020-4f266e3acab8
+ uuid:fadae2f8-0eea-1eea-9626-76a8f1d9bed4
+
+
+
+ The command was successful.
+ BIOS001
+ Yes
+ 0
+ Set PendingValue
+
+
+"""
diff --git a/ironic/tests/drivers/drac/test_bios.py b/ironic/tests/drivers/drac/test_bios.py
new file mode 100644
index 0000000000..4b063c60a2
--- /dev/null
+++ b/ironic/tests/drivers/drac/test_bios.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 Dell, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Test class for DRAC BIOS interface
+"""
+
+import mock
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.drac import bios
+from ironic.drivers.modules.drac import client as drac_client
+from ironic.drivers.modules.drac import management as drac_mgmt
+from ironic.drivers.modules.drac import resource_uris
+from ironic.tests.conductor import utils as mgr_utils
+from ironic.tests.db import base as db_base
+from ironic.tests.db import utils as db_utils
+from ironic.tests.drivers.drac import bios_wsman_mock
+from ironic.tests.drivers.drac import utils as test_utils
+from ironic.tests.objects import utils as obj_utils
+from six.moves.urllib.parse import unquote
+
+FAKE_DRAC = db_utils.get_test_drac_info()
+
+
+def _base_config(responses=[]):
+ for resource in [resource_uris.DCIM_BIOSEnumeration,
+ resource_uris.DCIM_BIOSString,
+ resource_uris.DCIM_BIOSInteger]:
+ xml_root = test_utils.mock_wsman_root(
+ bios_wsman_mock.Enumerations[resource]['XML'])
+ responses.append(xml_root)
+ return responses
+
+
+def _set_config(responses=[]):
+ ccj_xml = test_utils.build_soap_xml([{'DCIM_LifecycleJob':
+ {'Name': 'fake'}}],
+ resource_uris.DCIM_LifecycleJob)
+ responses.append(test_utils.mock_wsman_root(ccj_xml))
+ return _base_config(responses)
+
+
+def _mock_pywsman_responses(client, responses):
+ mpw = client.Client.return_value
+ mpw.enumerate.side_effect = responses
+ return mpw
+
+
+@mock.patch.object(drac_client, 'pywsman')
+class DracBiosTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(DracBiosTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_drac')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=FAKE_DRAC)
+
+ def test_get_config(self, client):
+ _mock_pywsman_responses(client, _base_config())
+ expected = {}
+ for resource in [resource_uris.DCIM_BIOSEnumeration,
+ resource_uris.DCIM_BIOSString,
+ resource_uris.DCIM_BIOSInteger]:
+ expected.update(bios_wsman_mock.Enumerations[resource]['Dict'])
+ result = bios.get_config(self.node)
+ self.assertEqual(expected, result)
+
+ def test_set_config_empty(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ res = bios.set_config(task)
+ self.assertFalse(res)
+
+ def test_set_config_nochange(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ res = bios.set_config(task,
+ MemTest='Disabled',
+ ProcCStates='Disabled',
+ SystemModelName='PowerEdge R630',
+ AssetTag=None,
+ Proc1NumCores=8,
+ AcPwrRcvryUserDelay=60)
+ self.assertFalse(res)
+
+ def test_set_config_ro(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ ProcCStates="Enabled")
+
+ def test_set_config_enum_invalid(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ MemTest="Never")
+
+ def test_set_config_string_toolong(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ tag = ('Never have I seen such a silly long asset tag! '
+ 'It is really rather ridiculous, don\'t you think?')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AssetTag=tag)
+
+ def test_set_config_string_nomatch(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ tag = unquote('%80')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AssetTag=tag)
+
+ def test_set_config_integer_toosmall(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AcPwrRcvryUserDelay=0)
+
+ def test_set_config_integer_toobig(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AcPwrRcvryUserDelay=600)
+
+ def test_set_config_needreboot(self, client):
+ mock_pywsman = _mock_pywsman_responses(client, _set_config())
+ invoke_xml = test_utils.mock_wsman_root(
+ bios_wsman_mock.Invoke_Commit)
+ # TODO(victor-lowther) This needs more work.
+ # Specifically, we will need to verify that
+ # invoke was handed the XML blob we expected.
+ mock_pywsman.invoke.return_value = invoke_xml
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ res = bios.set_config(task,
+ AssetTag="An Asset Tag",
+ MemTest="Enabled")
+ self.assertTrue(res)
+
+ @mock.patch.object(drac_mgmt, 'check_for_config_job',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
+ autospec=True)
+ def test_commit_config(self, mock_ccj, mock_cfcj, client):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ bios.commit_config(task)
+ self.assertTrue(mock_cfcj.called)
+ self.assertTrue(mock_ccj.called)
+
+ @mock.patch.object(drac_client.Client, 'wsman_invoke', spec_set=True,
+ autospec=True)
+ def test_abandon_config(self, mock_wi, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ bios.abandon_config(task)
+ self.assertTrue(mock_wi.called)
diff --git a/ironic/tests/drivers/drac/test_client.py b/ironic/tests/drivers/drac/test_client.py
index 565a551b0a..a7735c45d4 100644
--- a/ironic/tests/drivers/drac/test_client.py
+++ b/ironic/tests/drivers/drac/test_client.py
@@ -247,7 +247,8 @@ class DracClientTestCase(base.TestCase):
method_name = 'method'
client = drac_client.Client(**INFO_DICT)
self.assertRaises(exception.DracUnexpectedReturnValue,
- client.wsman_invoke, self.resource_uri, method_name)
+ client.wsman_invoke, self.resource_uri, method_name,
+ {}, {}, drac_client.RET_SUCCESS)
mock_options = mock_client_pywsman.ClientOptions.return_value
mock_pywsman_client.invoke.assert_called_once_with(
diff --git a/ironic/tests/drivers/drac/test_management.py b/ironic/tests/drivers/drac/test_management.py
index 930401db9c..9cc7f42c1f 100644
--- a/ironic/tests/drivers/drac/test_management.py
+++ b/ironic/tests/drivers/drac/test_management.py
@@ -94,7 +94,7 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
- result = drac_mgmt._check_for_config_job(self.node)
+ result = drac_mgmt.check_for_config_job(self.node)
self.assertIsNone(result)
mock_pywsman.enumerate.assert_called_once_with(
@@ -112,7 +112,7 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_pywsman.enumerate.return_value = mock_xml
self.assertRaises(exception.DracPendingConfigJobExists,
- drac_mgmt._check_for_config_job, self.node)
+ drac_mgmt.check_for_config_job, self.node)
mock_pywsman.enumerate.assert_called_once_with(
mock.ANY, mock.ANY, resource_uris.DCIM_LifecycleJob)
@@ -130,13 +130,13 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_pywsman.enumerate.return_value = mock_xml
try:
- drac_mgmt._check_for_config_job(self.node)
+ drac_mgmt.check_for_config_job(self.node)
except (exception.DracClientError,
exception.DracPendingConfigJobExists):
self.fail("Failed to detect completed job due to "
"\"{}\" job status".format(job_status))
- def test__create_config_job(self, mock_client_pywsman):
+ def test_create_config_job(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml(
[{'ReturnValue': drac_client.RET_CREATED}],
resource_uris.DCIM_BIOSService)
@@ -145,14 +145,14 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.invoke.return_value = mock_xml
- result = drac_mgmt._create_config_job(self.node)
+ result = drac_mgmt.create_config_job(self.node)
self.assertIsNone(result)
mock_pywsman.invoke.assert_called_once_with(
mock.ANY, resource_uris.DCIM_BIOSService,
'CreateTargetedConfigJob', None)
- def test__create_config_job_error(self, mock_client_pywsman):
+ def test_create_config_job_error(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml(
[{'ReturnValue': drac_client.RET_ERROR,
'Message': 'E_FAKE'}],
@@ -163,7 +163,7 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_pywsman.invoke.return_value = mock_xml
self.assertRaises(exception.DracOperationFailed,
- drac_mgmt._create_config_job, self.node)
+ drac_mgmt.create_config_job, self.node)
mock_pywsman.invoke.assert_called_once_with(
mock.ANY, resource_uris.DCIM_BIOSService,
'CreateTargetedConfigJob', None)
@@ -280,9 +280,9 @@ class DracManagementTestCase(db_base.DbTestCase):
autospec=True)
@mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
spec_set=True, autospec=True)
- @mock.patch.object(drac_mgmt, '_check_for_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
autospec=True)
- @mock.patch.object(drac_mgmt, '_create_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
autospec=True)
def test_set_boot_device(self, mock_ccj, mock_cfcj, mock_glcv, mock_gbd,
mock_client_pywsman):
@@ -323,9 +323,9 @@ class DracManagementTestCase(db_base.DbTestCase):
autospec=True)
@mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
spec_set=True, autospec=True)
- @mock.patch.object(drac_mgmt, '_check_for_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
autospec=True)
- @mock.patch.object(drac_mgmt, '_create_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
autospec=True)
def test_set_boot_device_fail(self, mock_ccj, mock_cfcj, mock_glcv,
mock_gbd, mock_client_pywsman):
@@ -368,7 +368,7 @@ class DracManagementTestCase(db_base.DbTestCase):
spec_set=True, autospec=True)
@mock.patch.object(drac_client.Client, 'wsman_enumerate', spec_set=True,
autospec=True)
- @mock.patch.object(drac_mgmt, '_check_for_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
autospec=True)
def test_set_boot_device_client_error(self, mock_cfcj, mock_we, mock_glcv,
mock_gbd,
@@ -394,7 +394,7 @@ class DracManagementTestCase(db_base.DbTestCase):
autospec=True)
@mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
spec_set=True, autospec=True)
- @mock.patch.object(drac_mgmt, '_check_for_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
autospec=True)
def test_set_boot_device_noop(self, mock_cfcj, mock_glcv, mock_gbd,
mock_client_pywsman):
@@ -419,9 +419,9 @@ class DracManagementTestCase(db_base.DbTestCase):
autospec=True)
@mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
spec_set=True, autospec=True)
- @mock.patch.object(drac_mgmt, '_check_for_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
autospec=True)
- @mock.patch.object(drac_mgmt, '_create_config_job', spec_set=True,
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
autospec=True)
def test_set_boot_device_11g(self, mock_ccj, mock_cfcj, mock_glcv,
mock_gbd, mock_client_pywsman):