Add DRAC BIOS config vendor passthru API

Change-Id: I1f02f8eb14b5aae34714ccaf4fd876a547272b1f
Co-Authored-By: Chris Dearborn <Christopher_Dearborn@dell.com>
Signed-off-by: Victor Lowther <victor.lowther@gmail.com>
This commit is contained in:
Victor Lowther 2015-01-26 15:30:41 -06:00 committed by Lucas Alvares Gomes
parent 78737b5e28
commit a870b97799
10 changed files with 1107 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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': """<ns0:Envelope
xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
xmlns:ns3="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
xmlns:ns4="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSEnumeration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ns0:Header>
<ns1:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
</ns1:To>
<ns1:Action>
http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse</ns1:Action>
<ns1:RelatesTo>uuid:1f5cd907-0e6f-1e6f-8002-4f266e3acab8</ns1:RelatesTo>
<ns1:MessageID>uuid:219ca357-0e6f-1e6f-a828-f0e4fb722ab8</ns1:MessageID>
</ns0:Header>
<ns0:Body>
<ns2:EnumerateResponse>
<ns3:Items>
<ns4:DCIM_BIOSEnumeration>
<ns4:AttributeName>MemTest</ns4:AttributeName>
<ns4:CurrentValue>Disabled</ns4:CurrentValue>
<ns4:Dependency xsi:nil="true" />
<ns4:DisplayOrder>310</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>Memory Settings</ns4:GroupDisplayName>
<ns4:GroupID>MemSettings</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:MemTest</ns4:InstanceID>
<ns4:IsReadOnly>false</ns4:IsReadOnly>
<ns4:PendingValue xsi:nil="true" />
<ns4:PossibleValues>Enabled</ns4:PossibleValues>
<ns4:PossibleValues>Disabled</ns4:PossibleValues>
</ns4:DCIM_BIOSEnumeration>
<ns4:DCIM_BIOSEnumeration>
<ns4:AttributeDisplayName>C States</ns4:AttributeDisplayName>
<ns4:AttributeName>ProcCStates</ns4:AttributeName>
<ns4:CurrentValue>Disabled</ns4:CurrentValue>
<ns4:DisplayOrder>1706</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>System Profile Settings</ns4:GroupDisplayName>
<ns4:GroupID>SysProfileSettings</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:ProcCStates</ns4:InstanceID>
<ns4:IsReadOnly>true</ns4:IsReadOnly>
<ns4:PendingValue xsi:nil="true" />
<ns4:PossibleValues>Enabled</ns4:PossibleValues>
<ns4:PossibleValues>Disabled</ns4:PossibleValues>
</ns4:DCIM_BIOSEnumeration>
</ns3:Items>
</ns2:EnumerateResponse>
</ns0:Body>
</ns0:Envelope>""",
'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': """<ns0:Envelope
xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
xmlns:ns3="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
xmlns:ns4="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSString"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ns0:Header>
<ns1:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
</ns1:To>
<ns1:Action>
http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse
</ns1:Action>
<ns1:RelatesTo>uuid:1f877bcb-0e6f-1e6f-8004-4f266e3acab8</ns1:RelatesTo>
<ns1:MessageID>uuid:21bea321-0e6f-1e6f-a82b-f0e4fb722ab8</ns1:MessageID>
</ns0:Header>
<ns0:Body>
<ns2:EnumerateResponse>
<ns3:Items>
<ns4:DCIM_BIOSString>
<ns4:AttributeName>SystemModelName</ns4:AttributeName>
<ns4:CurrentValue>PowerEdge R630</ns4:CurrentValue>
<ns4:Dependency xsi:nil="true" />
<ns4:DisplayOrder>201</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>System Information</ns4:GroupDisplayName>
<ns4:GroupID>SysInformation</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:SystemModelName</ns4:InstanceID>
<ns4:IsReadOnly>true</ns4:IsReadOnly>
<ns4:MaxLength>40</ns4:MaxLength>
<ns4:MinLength>0</ns4:MinLength>
<ns4:PendingValue xsi:nil="true" />
<ns4:ValueExpression xsi:nil="true" />
</ns4:DCIM_BIOSString>
<ns4:DCIM_BIOSString>
<ns4:AttributeName>SystemModelName2</ns4:AttributeName>
<ns4:CurrentValue>PowerEdge R630</ns4:CurrentValue>
<ns4:Dependency xsi:nil="true" />
<ns4:DisplayOrder>201</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>System Information</ns4:GroupDisplayName>
<ns4:GroupID>SysInformation</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:SystemModelName2</ns4:InstanceID>
<ns4:IsReadOnly>true</ns4:IsReadOnly>
<ns4:MaxLength>40</ns4:MaxLength>
<ns4:MinLength>0</ns4:MinLength>
<ns4:PendingValue xsi:nil="true" />
</ns4:DCIM_BIOSString>
<ns4:DCIM_BIOSString>
<ns4:AttributeDisplayName>Asset Tag</ns4:AttributeDisplayName>
<ns4:AttributeName>AssetTag</ns4:AttributeName>
<ns4:CurrentValue xsi:nil="true" />
<ns4:Dependency xsi:nil="true" />
<ns4:DisplayOrder>1903</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>Miscellaneous Settings</ns4:GroupDisplayName>
<ns4:GroupID>MiscSettings</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:AssetTag</ns4:InstanceID>
<ns4:IsReadOnly>false</ns4:IsReadOnly>
<ns4:MaxLength>63</ns4:MaxLength>
<ns4:MinLength>0</ns4:MinLength>
<ns4:PendingValue xsi:nil="true" />
<ns4:ValueExpression>^[ -~]{0,63}$</ns4:ValueExpression>
</ns4:DCIM_BIOSString>
</ns3:Items>
<ns2:EnumerationContext />
<ns3:EndOfSequence />
</ns2:EnumerateResponse>
</ns0:Body>
</ns0:Envelope>""",
'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': """<ns0:Envelope
xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
xmlns:ns3="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
xmlns:ns4="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSInteger"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ns0:Header>
<ns1:To>
http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</ns1:To>
<ns1:Action>
http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse</ns1:Action>
<ns1:RelatesTo>uuid:1fa60792-0e6f-1e6f-8005-4f266e3acab8</ns1:RelatesTo>
<ns1:MessageID>uuid:21ccf01d-0e6f-1e6f-a82d-f0e4fb722ab8</ns1:MessageID>
</ns0:Header>
<ns0:Body>
<ns2:EnumerateResponse>
<ns3:Items>
<ns4:DCIM_BIOSInteger>
<ns4:AttributeName>Proc1NumCores</ns4:AttributeName>
<ns4:CurrentValue>8</ns4:CurrentValue>
<ns4:Dependency xsi:nil="true" />
<ns4:DisplayOrder>439</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>Processor Settings</ns4:GroupDisplayName>
<ns4:GroupID>ProcSettings</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:Proc1NumCores</ns4:InstanceID>
<ns4:IsReadOnly>true</ns4:IsReadOnly>
<ns4:LowerBound>0</ns4:LowerBound>
<ns4:PendingValue xsi:nil="true" />
<ns4:UpperBound>65535</ns4:UpperBound>
</ns4:DCIM_BIOSInteger>
<ns4:DCIM_BIOSInteger>
<ns4:AttributeName>AcPwrRcvryUserDelay</ns4:AttributeName>
<ns4:CurrentValue>60</ns4:CurrentValue>
<ns4:DisplayOrder>1825</ns4:DisplayOrder>
<ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
<ns4:GroupDisplayName>System Security</ns4:GroupDisplayName>
<ns4:GroupID>SysSecurity</ns4:GroupID>
<ns4:InstanceID>BIOS.Setup.1-1:AcPwrRcvryUserDelay</ns4:InstanceID>
<ns4:IsReadOnly>false</ns4:IsReadOnly>
<ns4:LowerBound>60</ns4:LowerBound>
<ns4:PendingValue xsi:nil="true" />
<ns4:UpperBound>240</ns4:UpperBound>
</ns4:DCIM_BIOSInteger>
</ns3:Items>
<ns2:EnumerationContext />
<ns3:EndOfSequence />
</ns2:EnumerateResponse>
</ns0:Body>
</ns0:Envelope>""",
'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 = """<ns0:Envelope
xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:ns2="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService">
<ns0:Header>
<ns1:To>
http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</ns1:To>
<ns1:Action>
http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/SetAttributesResponse</ns1:Action>
<ns1:RelatesTo>uuid:42baa476-0ee9-1ee9-8020-4f266e3acab8</ns1:RelatesTo>
<ns1:MessageID>uuid:fadae2f8-0eea-1eea-9626-76a8f1d9bed4</ns1:MessageID>
</ns0:Header>
<ns0:Body>
<ns2:SetAttributes_OUTPUT>
<ns2:Message>The command was successful.</ns2:Message>
<ns2:MessageID>BIOS001</ns2:MessageID>
<ns2:RebootRequired>Yes</ns2:RebootRequired>
<ns2:ReturnValue>0</ns2:ReturnValue>
<ns2:SetResult>Set PendingValue</ns2:SetResult>
</ns2:SetAttributes_OUTPUT>
</ns0:Body>
</ns0:Envelope>"""

View File

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

View File

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

View File

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