Merge "DRAC: switch to python-dracclient on vendor-passthru"
This commit is contained in:
commit
22402e4586
@ -29,6 +29,7 @@ from ironic.drivers.modules.cimc import management as cimc_mgmt
|
|||||||
from ironic.drivers.modules.cimc import power as cimc_power
|
from ironic.drivers.modules.cimc import power as cimc_power
|
||||||
from ironic.drivers.modules.drac import management as drac_mgmt
|
from ironic.drivers.modules.drac import management as drac_mgmt
|
||||||
from ironic.drivers.modules.drac import power as drac_power
|
from ironic.drivers.modules.drac import power as drac_power
|
||||||
|
from ironic.drivers.modules.drac import vendor_passthru as drac_vendor
|
||||||
from ironic.drivers.modules import fake
|
from ironic.drivers.modules import fake
|
||||||
from ironic.drivers.modules import iboot
|
from ironic.drivers.modules import iboot
|
||||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||||
@ -191,6 +192,7 @@ class FakeDracDriver(base.BaseDriver):
|
|||||||
self.power = drac_power.DracPower()
|
self.power = drac_power.DracPower()
|
||||||
self.deploy = fake.FakeDeploy()
|
self.deploy = fake.FakeDeploy()
|
||||||
self.management = drac_mgmt.DracManagement()
|
self.management = drac_mgmt.DracManagement()
|
||||||
|
self.vendor = drac_vendor.DracVendorPassthru()
|
||||||
|
|
||||||
|
|
||||||
class FakeSNMPDriver(base.BaseDriver):
|
class FakeSNMPDriver(base.BaseDriver):
|
||||||
|
@ -12,211 +12,27 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
DRAC Bios specific methods
|
DRAC BIOS configuration specific methods
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
from xml.etree import ElementTree as ET
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _, _LE, _LW
|
from ironic.common.i18n import _LE
|
||||||
from ironic.conductor import task_manager
|
from ironic.drivers.modules.drac import common as drac_common
|
||||||
from ironic.drivers.modules.drac import client as wsman_client
|
from ironic.drivers.modules.drac import job as drac_job
|
||||||
from ironic.drivers.modules.drac import management
|
|
||||||
from ironic.drivers.modules.drac import resource_uris
|
drac_exceptions = importutils.try_import('dracclient.exceptions')
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
def get_config(node):
|
||||||
"""Get the BIOS configuration from a Dell server using WSMAN
|
"""Get the BIOS configuration.
|
||||||
|
|
||||||
:param node: an ironic node object.
|
:param node: an ironic node object.
|
||||||
:raises: DracClientError on an error from pywsman.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
:raises: DracOperationFailed when a BIOS setting cannot be parsed.
|
|
||||||
:returns: a dictionary containing BIOS settings in the form of:
|
:returns: a dictionary containing BIOS settings in the form of:
|
||||||
{'EnumAttrib': {'name': 'EnumAttrib',
|
{'EnumAttrib': {'name': 'EnumAttrib',
|
||||||
'current_value': 'Value',
|
'current_value': 'Value',
|
||||||
@ -267,166 +83,85 @@ def get_config(node):
|
|||||||
Integer attributes also have the following parameters:
|
Integer attributes also have the following parameters:
|
||||||
:lower_bound: is the minimum value the attribute can have.
|
:lower_bound: is the minimum value the attribute can have.
|
||||||
:upper_bound: is the maximum value the attribute can have.
|
:upper_bound: is the maximum value the attribute can have.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
res = {}
|
|
||||||
for ns in [resource_uris.DCIM_BIOSEnumeration,
|
client = drac_common.get_drac_client(node)
|
||||||
resource_uris.DCIM_BIOSString,
|
|
||||||
resource_uris.DCIM_BIOSInteger]:
|
try:
|
||||||
attribs = _get_config(node, ns)
|
return client.list_bios_settings()
|
||||||
if not set(res).isdisjoint(set(attribs)):
|
except drac_exceptions.BaseClientException as exc:
|
||||||
raise exception.DracOperationFailed(
|
LOG.error(_LE('DRAC driver failed to get the BIOS settings for node '
|
||||||
message=_('Colliding attributes %r') % (
|
'%(node_uuid)s. Reason: %(error)s.'),
|
||||||
set(res) & set(attribs)))
|
{'node_uuid': node.uuid,
|
||||||
res.update(attribs)
|
'error': exc})
|
||||||
return res
|
raise exception.DracOperationError(error=exc)
|
||||||
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def set_config(task, **kwargs):
|
def set_config(task, **kwargs):
|
||||||
"""Sets the pending_value parameter for each of the values passed in.
|
"""Sets the pending_value parameter for each of the values passed in.
|
||||||
|
|
||||||
:param task: an ironic task object.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param kwargs: a dictionary of {'AttributeName': 'NewValue'}
|
:param kwargs: a dictionary of {'AttributeName': 'NewValue'}
|
||||||
:raises: DracOperationFailed if any new values are invalid.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
:raises: DracOperationFailed if any of the attributes are read-only.
|
:returns: A dictionary containing the commit_required key with a boolean
|
||||||
:raises: DracOperationFailed if any of the attributes cannot be set for
|
value indicating whether commit_bios_config() needs to be called
|
||||||
any other reason.
|
to make the changes.
|
||||||
: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
|
node = task.node
|
||||||
management.check_for_config_job(node)
|
drac_job.validate_job_queue(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
|
client = drac_common.get_drac_client(node)
|
||||||
read_only_keys = []
|
if 'http_method' in kwargs:
|
||||||
unchanged_attribs = []
|
del kwargs['http_method']
|
||||||
invalid_attribs_msgs = []
|
|
||||||
attrib_names = []
|
|
||||||
|
|
||||||
for k in candidates:
|
try:
|
||||||
if str(kwargs[k]) == str(current[k]['current_value']):
|
return client.set_bios_settings(kwargs)
|
||||||
unchanged_attribs.append(k)
|
except drac_exceptions.BaseClientException as exc:
|
||||||
elif current[k]['read_only']:
|
LOG.error(_LE('DRAC driver failed to set the BIOS settings for node '
|
||||||
read_only_keys.append(k)
|
'%(node_uuid)s. Reason: %(error)s.'),
|
||||||
else:
|
{'node_uuid': node.uuid,
|
||||||
if 'possible_values' in current[k]:
|
'error': exc})
|
||||||
if str(kwargs[k]) not in current[k]['possible_values']:
|
raise exception.DracOperationError(error=exc)
|
||||||
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.warning(_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, reboot=False):
|
def commit_config(task, reboot=False):
|
||||||
"""Commits pending changes added by set_config
|
"""Commits pending changes added by set_config
|
||||||
|
|
||||||
:param task: is the ironic task for running the config job.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param reboot: indicates whether a reboot job should be automatically
|
:param reboot: indicates whether a reboot job should be automatically
|
||||||
created with the config job.
|
created with the config job.
|
||||||
:raises: DracClientError on an error from pywsman library.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
:raises: DracPendingConfigJobExists if the job is already created.
|
:returns: the job_id key with the id of the newly created config job.
|
||||||
: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
|
node = task.node
|
||||||
management.check_for_config_job(node)
|
drac_job.validate_job_queue(node)
|
||||||
management.create_config_job(node, reboot)
|
|
||||||
|
client = drac_common.get_drac_client(node)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return client.commit_pending_bios_changes(reboot)
|
||||||
|
except drac_exceptions.BaseClientException as exc:
|
||||||
|
LOG.error(_LE('DRAC driver failed to commit the pending BIOS changes '
|
||||||
|
'for node %(node_uuid)s. Reason: %(error)s.'),
|
||||||
|
{'node_uuid': node.uuid,
|
||||||
|
'error': exc})
|
||||||
|
raise exception.DracOperationError(error=exc)
|
||||||
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def abandon_config(task):
|
def abandon_config(task):
|
||||||
"""Abandons uncommitted changes added by set_config
|
"""Abandons uncommitted changes added by set_config
|
||||||
|
|
||||||
:param task: is the ironic task for abandoning the changes.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:raises: DracClientError on an error from pywsman library.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
:raises: DracOperationFailed on error reported back by DRAC.
|
|
||||||
:raises: DracUnexpectedReturnValue if the drac did not report success.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
node = task.node
|
node = task.node
|
||||||
client = wsman_client.get_wsman_client(node)
|
client = drac_common.get_drac_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,
|
try:
|
||||||
'DeletePendingConfiguration',
|
client.abandon_pending_bios_changes()
|
||||||
selectors,
|
except drac_exceptions.BaseClientException as exc:
|
||||||
properties,
|
LOG.error(_LE('DRAC driver failed to delete the pending BIOS '
|
||||||
wsman_client.RET_SUCCESS)
|
'settings for node %(node_uuid)s. Reason: %(error)s.'),
|
||||||
|
{'node_uuid': node.uuid,
|
||||||
|
'error': exc})
|
||||||
|
raise exception.DracOperationError(error=exc)
|
||||||
|
@ -20,7 +20,6 @@ DRAC management interface
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
@ -29,11 +28,8 @@ from ironic.common.i18n import _
|
|||||||
from ironic.common.i18n import _LE
|
from ironic.common.i18n import _LE
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules.drac import client as drac_client
|
|
||||||
from ironic.drivers.modules.drac import common as drac_common
|
from ironic.drivers.modules.drac import common as drac_common
|
||||||
from ironic.drivers.modules.drac import job as drac_job
|
from ironic.drivers.modules.drac import job as drac_job
|
||||||
from ironic.drivers.modules.drac import resource_uris
|
|
||||||
|
|
||||||
|
|
||||||
drac_exceptions = importutils.try_import('dracclient.exceptions')
|
drac_exceptions = importutils.try_import('dracclient.exceptions')
|
||||||
|
|
||||||
@ -45,11 +41,6 @@ _BOOT_DEVICES_MAP = {
|
|||||||
boot_devices.CDROM: 'Optical',
|
boot_devices.CDROM: 'Optical',
|
||||||
}
|
}
|
||||||
|
|
||||||
TARGET_DEVICE = 'BIOS.Setup.1-1'
|
|
||||||
|
|
||||||
# RebootJobType constants
|
|
||||||
_GRACEFUL_REBOOT_WITH_FORCED_SHUTDOWN = '3'
|
|
||||||
|
|
||||||
# BootMode constants
|
# BootMode constants
|
||||||
PERSISTENT_BOOT_MODE = 'IPL'
|
PERSISTENT_BOOT_MODE = 'IPL'
|
||||||
NON_PERSISTENT_BOOT_MODE = 'OneTime'
|
NON_PERSISTENT_BOOT_MODE = 'OneTime'
|
||||||
@ -127,86 +118,6 @@ def set_boot_device(node, device, persistent=False):
|
|||||||
raise exception.DracOperationError(error=exc)
|
raise exception.DracOperationError(error=exc)
|
||||||
|
|
||||||
|
|
||||||
# TODO(ifarkas): delete this during BIOS vendor_passthru refactor
|
|
||||||
def create_config_job(node, reboot=False):
|
|
||||||
"""Create a configuration job.
|
|
||||||
|
|
||||||
This method is used to apply the pending values created by
|
|
||||||
set_boot_device().
|
|
||||||
|
|
||||||
:param node: an ironic node object.
|
|
||||||
:param reboot: indicates whether a reboot job should be automatically
|
|
||||||
created with the config job.
|
|
||||||
:raises: DracClientError if the client received unexpected response.
|
|
||||||
:raises: DracOperationFailed if the client received response with an
|
|
||||||
error message.
|
|
||||||
:raises: DracUnexpectedReturnValue if the client received a response
|
|
||||||
with unexpected return value.
|
|
||||||
"""
|
|
||||||
client = drac_client.get_wsman_client(node)
|
|
||||||
selectors = {'CreationClassName': 'DCIM_BIOSService',
|
|
||||||
'Name': 'DCIM:BIOSService',
|
|
||||||
'SystemCreationClassName': 'DCIM_ComputerSystem',
|
|
||||||
'SystemName': 'DCIM:ComputerSystem'}
|
|
||||||
properties = {'Target': TARGET_DEVICE,
|
|
||||||
'ScheduledStartTime': 'TIME_NOW'}
|
|
||||||
|
|
||||||
if reboot:
|
|
||||||
properties['RebootJobType'] = _GRACEFUL_REBOOT_WITH_FORCED_SHUTDOWN
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.wsman_invoke(resource_uris.DCIM_BIOSService,
|
|
||||||
'CreateTargetedConfigJob', selectors, properties,
|
|
||||||
drac_client.RET_CREATED)
|
|
||||||
except exception.DracRequestFailed as exc:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.error(_LE('DRAC driver failed to create config job for node '
|
|
||||||
'%(node_uuid)s. The changes are not applied. '
|
|
||||||
'Reason: %(error)s.'),
|
|
||||||
{'node_uuid': node.uuid, 'error': exc})
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(ifarkas): delete this during BIOS vendor_passthru refactor
|
|
||||||
def check_for_config_job(node):
|
|
||||||
"""Check if a configuration job is already created.
|
|
||||||
|
|
||||||
:param node: an ironic node object.
|
|
||||||
:raises: DracClientError on an error from pywsman library.
|
|
||||||
:raises: DracPendingConfigJobExists if the job is already created.
|
|
||||||
|
|
||||||
"""
|
|
||||||
client = drac_client.get_wsman_client(node)
|
|
||||||
try:
|
|
||||||
doc = client.wsman_enumerate(resource_uris.DCIM_LifecycleJob)
|
|
||||||
except exception.DracClientError as exc:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.error(_LE('DRAC driver failed to list the configuration jobs '
|
|
||||||
'for node %(node_uuid)s. Reason: %(error)s.'),
|
|
||||||
{'node_uuid': node.uuid, 'error': exc})
|
|
||||||
|
|
||||||
items = drac_common.find_xml(doc, 'DCIM_LifecycleJob',
|
|
||||||
resource_uris.DCIM_LifecycleJob,
|
|
||||||
find_all=True)
|
|
||||||
for i in items:
|
|
||||||
name = drac_common.find_xml(i, 'Name', resource_uris.DCIM_LifecycleJob)
|
|
||||||
if TARGET_DEVICE not in name.text:
|
|
||||||
continue
|
|
||||||
|
|
||||||
job_status = drac_common.find_xml(
|
|
||||||
i, 'JobStatus', resource_uris.DCIM_LifecycleJob).text
|
|
||||||
# If job is already completed or failed we can
|
|
||||||
# create another one.
|
|
||||||
# The 'Completed with Errors' JobStatus can be returned by
|
|
||||||
# configuration jobs that set NIC or BIOS attributes.
|
|
||||||
# Job Control Documentation: http://goo.gl/o1dDD3 (Section 7.2.3.2)
|
|
||||||
if job_status.lower() not in ('completed', 'completed with errors',
|
|
||||||
'failed'):
|
|
||||||
job_id = drac_common.find_xml(i, 'InstanceID',
|
|
||||||
resource_uris.DCIM_LifecycleJob).text
|
|
||||||
raise exception.DracPendingConfigJobExists(job_id=job_id,
|
|
||||||
target=TARGET_DEVICE)
|
|
||||||
|
|
||||||
|
|
||||||
class DracManagement(base.ManagementInterface):
|
class DracManagement(base.ManagementInterface):
|
||||||
|
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
DRAC VendorPassthruBios Driver
|
DRAC VendorPassthruBios Driver
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from ironic.conductor import task_manager
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules.drac import bios
|
from ironic.drivers.modules.drac import bios
|
||||||
from ironic.drivers.modules.drac import common as drac_common
|
from ironic.drivers.modules.drac import common as drac_common
|
||||||
@ -24,97 +25,89 @@ class DracVendorPassthru(base.VendorInterface):
|
|||||||
"""Interface for DRAC specific BIOS configuration methods."""
|
"""Interface for DRAC specific BIOS configuration methods."""
|
||||||
|
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
"""Returns the driver_info properties.
|
"""Return the properties of the interface."""
|
||||||
|
|
||||||
This method returns the driver_info properties for this driver.
|
|
||||||
|
|
||||||
:returns: a dictionary of propery names and their descriptions.
|
|
||||||
"""
|
|
||||||
return drac_common.COMMON_PROPERTIES
|
return drac_common.COMMON_PROPERTIES
|
||||||
|
|
||||||
def validate(self, task, **kwargs):
|
def validate(self, task, **kwargs):
|
||||||
"""Validates the driver_info of a node.
|
"""Validate the driver-specific info supplied.
|
||||||
|
|
||||||
This method validates the driver_info associated with the node that is
|
This method validates whether the 'driver_info' property of the
|
||||||
associated with the task.
|
supplied node contains the required information for this driver to
|
||||||
|
manage the power state of the node.
|
||||||
|
|
||||||
:param task: the ironic task used to identify the node.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param kwargs: not used.
|
:param kwargs: not used.
|
||||||
:raises: InvalidParameterValue if mandatory information is missing on
|
:raises: InvalidParameterValue if required driver_info attribute
|
||||||
the node or any driver_info is invalid.
|
is missing or invalid on the node.
|
||||||
:returns: a dict containing information from driver_info
|
|
||||||
and default values.
|
|
||||||
"""
|
"""
|
||||||
return drac_common.parse_driver_info(task.node)
|
return drac_common.parse_driver_info(task.node)
|
||||||
|
|
||||||
@base.passthru(['GET'], async=False)
|
@base.passthru(['GET'], async=False)
|
||||||
def get_bios_config(self, task, **kwargs):
|
def get_bios_config(self, task, **kwargs):
|
||||||
"""Get BIOS settings.
|
"""Get the BIOS configuration.
|
||||||
|
|
||||||
This method is used to retrieve the BIOS settings from a node.
|
This method is used to retrieve the BIOS settings from a node.
|
||||||
|
|
||||||
:param task: the ironic task used to identify the node.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param kwargs: not used.
|
:param kwargs: not used.
|
||||||
:raises: DracClientError on an error from pywsman.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
:raises: DracOperationFailed when a BIOS setting cannot be parsed.
|
|
||||||
:returns: a dictionary containing BIOS settings.
|
:returns: a dictionary containing BIOS settings.
|
||||||
"""
|
"""
|
||||||
return bios.get_config(task.node)
|
bios_attrs = {}
|
||||||
|
for name, bios_attr in bios.get_config(task.node).items():
|
||||||
|
# NOTE(ifarkas): call from python-dracclient returns list of
|
||||||
|
# namedtuples, converting it to dict here.
|
||||||
|
bios_attrs[name] = bios_attr.__dict__
|
||||||
|
|
||||||
|
return bios_attrs
|
||||||
|
|
||||||
@base.passthru(['POST'], async=False)
|
@base.passthru(['POST'], async=False)
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
def set_bios_config(self, task, **kwargs):
|
def set_bios_config(self, task, **kwargs):
|
||||||
"""Change BIOS settings.
|
"""Change BIOS settings.
|
||||||
|
|
||||||
This method is used to change the BIOS settings on a node.
|
This method is used to change the BIOS settings on a node.
|
||||||
|
|
||||||
:param task: the ironic task used to identify the node.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param kwargs: a dictionary of {'AttributeName': 'NewValue'}
|
:param kwargs: a dictionary of {'AttributeName': 'NewValue'}
|
||||||
:raises: DracOperationFailed if any of the attributes cannot be set for
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
any reason.
|
:returns: A dictionary containing the commit_required key with a
|
||||||
:raises: DracClientError on an error from the pywsman library.
|
Boolean value indicating whether commit_bios_config() needs
|
||||||
:returns: A dictionary containing the commit_needed key with a boolean
|
to be called to make the changes.
|
||||||
value indicating whether commit_config() needs to be called
|
|
||||||
to make the changes.
|
|
||||||
"""
|
"""
|
||||||
return {'commit_needed': bios.set_config(task, **kwargs)}
|
return bios.set_config(task, **kwargs)
|
||||||
|
|
||||||
@base.passthru(['POST'], async=False)
|
@base.passthru(['POST'], async=False)
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
def commit_bios_config(self, task, reboot=False, **kwargs):
|
def commit_bios_config(self, task, reboot=False, **kwargs):
|
||||||
"""Commit a BIOS configuration job.
|
"""Commit a BIOS configuration job.
|
||||||
|
|
||||||
This method is used to commit a BIOS configuration job.
|
This method is used to commit a BIOS configuration job.
|
||||||
submitted through set_bios_config().
|
submitted through set_bios_config().
|
||||||
|
|
||||||
:param task: the ironic task for running the config job.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param reboot: indicates whether a reboot job should be automatically
|
:param reboot: indicates whether a reboot job should be automatically
|
||||||
created with the config job.
|
created with the config job.
|
||||||
:param kwargs: additional arguments sent via vendor passthru.
|
:param kwargs: not used.
|
||||||
:raises: DracClientError on an error from pywsman library.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
:raises: DracPendingConfigJobExists if the job is already created.
|
:returns: A dictionary containing the job_id key with the id of the
|
||||||
:raises: DracOperationFailed if the client received response with an
|
newly created config job, and the reboot_required key
|
||||||
error message.
|
indicating whether to node needs to be rebooted to start the
|
||||||
:raises: DracUnexpectedReturnValue if the client received a response
|
config job.
|
||||||
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, reboot=reboot)
|
job_id = bios.commit_config(task, reboot=reboot)
|
||||||
return {'committing': None, 'reboot_needed': not reboot}
|
return {'job_id': job_id, 'reboot_required': not reboot}
|
||||||
|
|
||||||
@base.passthru(['DELETE'], async=False)
|
@base.passthru(['DELETE'], async=False)
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
def abandon_bios_config(self, task, **kwargs):
|
def abandon_bios_config(self, task, **kwargs):
|
||||||
"""Abandon a BIOS configuration job.
|
"""Abandon a BIOS configuration job.
|
||||||
|
|
||||||
This method is used to abandon a BIOS configuration job previously
|
This method is used to abandon a BIOS configuration previously
|
||||||
submitted through set_bios_config().
|
submitted through set_bios_config().
|
||||||
|
|
||||||
:param task: the ironic task for abandoning the changes.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
:param kwargs: not used.
|
:param kwargs: not used.
|
||||||
:raises: DracClientError on an error from pywsman library.
|
:raises: DracOperationError on an error from python-dracclient.
|
||||||
: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)
|
bios.abandon_config(task)
|
||||||
return {'abandoned': None}
|
|
||||||
|
@ -16,196 +16,150 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Test class for DRAC BIOS interface
|
Test class for DRAC BIOS configuration specific methods
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from dracclient import exceptions as drac_exceptions
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.drivers.modules.drac import bios
|
from ironic.drivers.modules.drac import common as drac_common
|
||||||
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.unit.conductor import mgr_utils
|
from ironic.tests.unit.conductor import mgr_utils
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
from ironic.tests.unit.drivers.modules.drac import bios_wsman_mock
|
|
||||||
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
from six.moves.urllib.parse import unquote
|
|
||||||
|
|
||||||
FAKE_DRAC = db_utils.get_test_drac_info()
|
INFO_DICT = db_utils.get_test_drac_info()
|
||||||
|
|
||||||
|
|
||||||
def _base_config(responses=[]):
|
class DracBIOSConfigurationTestCase(db_base.DbTestCase):
|
||||||
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):
|
def setUp(self):
|
||||||
super(DracBiosTestCase, self).setUp()
|
super(DracBIOSConfigurationTestCase, self).setUp()
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_drac')
|
mgr_utils.mock_the_extension_manager(driver='fake_drac')
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
driver='fake_drac',
|
driver='fake_drac',
|
||||||
driver_info=FAKE_DRAC)
|
driver_info=INFO_DICT)
|
||||||
|
|
||||||
def test_get_config(self, client):
|
patch_get_drac_client = mock.patch.object(
|
||||||
_mock_pywsman_responses(client, _base_config())
|
drac_common, 'get_drac_client', spec_set=True, autospec=True)
|
||||||
expected = {}
|
mock_get_drac_client = patch_get_drac_client.start()
|
||||||
for resource in [resource_uris.DCIM_BIOSEnumeration,
|
self.mock_client = mock.Mock()
|
||||||
resource_uris.DCIM_BIOSString,
|
mock_get_drac_client.return_value = self.mock_client
|
||||||
resource_uris.DCIM_BIOSInteger]:
|
self.addCleanup(patch_get_drac_client.stop)
|
||||||
expected.update(bios_wsman_mock.Enumerations[resource]['Dict'])
|
|
||||||
result = bios.get_config(self.node)
|
proc_virt_attr = {
|
||||||
self.assertEqual(expected, result)
|
'name': 'ProcVirtualization',
|
||||||
|
'current_value': 'Enabled',
|
||||||
|
'pending_value': None,
|
||||||
|
'read_only': False,
|
||||||
|
'possible_values': ['Enabled', 'Disabled']}
|
||||||
|
self.bios_attrs = {
|
||||||
|
'ProcVirtualization': mock.Mock(**proc_virt_attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_get_config(self):
|
||||||
|
self.mock_client.list_bios_settings.return_value = self.bios_attrs
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
bios_config = task.driver.vendor.get_bios_config(task)
|
||||||
|
|
||||||
|
self.mock_client.list_bios_settings.assert_called_once_with()
|
||||||
|
self.assertIn('ProcVirtualization', bios_config)
|
||||||
|
|
||||||
|
def test_get_config_fail(self):
|
||||||
|
exc = drac_exceptions.BaseClientException('boom')
|
||||||
|
self.mock_client.list_bios_settings.side_effect = exc
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.DracOperationError,
|
||||||
|
task.driver.vendor.get_bios_config, task)
|
||||||
|
|
||||||
|
self.mock_client.list_bios_settings.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_set_config(self):
|
||||||
|
self.mock_client.list_jobs.return_value = []
|
||||||
|
|
||||||
def test_set_config_empty(self, client):
|
|
||||||
_mock_pywsman_responses(client, _set_config())
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
res = bios.set_config(task)
|
task.driver.vendor.set_bios_config(task,
|
||||||
self.assertFalse(res)
|
ProcVirtualization='Enabled')
|
||||||
|
|
||||||
|
self.mock_client.list_jobs.assert_called_once_with(
|
||||||
|
only_unfinished=True)
|
||||||
|
self.mock_client.set_bios_settings.assert_called_once_with(
|
||||||
|
{'ProcVirtualization': 'Enabled'})
|
||||||
|
|
||||||
|
def test_set_config_fail(self):
|
||||||
|
self.mock_client.list_jobs.return_value = []
|
||||||
|
exc = drac_exceptions.BaseClientException('boom')
|
||||||
|
self.mock_client.set_bios_settings.side_effect = exc
|
||||||
|
|
||||||
def test_set_config_nochange(self, client):
|
|
||||||
_mock_pywsman_responses(client, _set_config())
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
task.node = self.node
|
self.assertRaises(exception.DracOperationError,
|
||||||
res = bios.set_config(task,
|
task.driver.vendor.set_bios_config, task,
|
||||||
MemTest='Disabled',
|
ProcVirtualization='Enabled')
|
||||||
ProcCStates='Disabled',
|
|
||||||
SystemModelName='PowerEdge R630',
|
self.mock_client.set_bios_settings.assert_called_once_with(
|
||||||
AssetTag=None,
|
{'ProcVirtualization': 'Enabled'})
|
||||||
Proc1NumCores=8,
|
|
||||||
AcPwrRcvryUserDelay=60)
|
def test_commit_config(self):
|
||||||
self.assertFalse(res)
|
self.mock_client.list_jobs.return_value = []
|
||||||
|
|
||||||
def test_set_config_ro(self, client):
|
|
||||||
_mock_pywsman_responses(client, _set_config())
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
task.node = self.node
|
task.driver.vendor.commit_bios_config(task)
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
|
||||||
bios.set_config, task,
|
self.mock_client.list_jobs.assert_called_once_with(
|
||||||
ProcCStates="Enabled")
|
only_unfinished=True)
|
||||||
|
self.mock_client.commit_pending_bios_changes.assert_called_once_with(
|
||||||
|
False)
|
||||||
|
|
||||||
|
def test_commit_config_with_reboot(self):
|
||||||
|
self.mock_client.list_jobs.return_value = []
|
||||||
|
|
||||||
def test_set_config_enum_invalid(self, client):
|
|
||||||
_mock_pywsman_responses(client, _set_config())
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
task.node = self.node
|
task.driver.vendor.commit_bios_config(task, reboot=True)
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
|
||||||
bios.set_config, task,
|
self.mock_client.list_jobs.assert_called_once_with(
|
||||||
MemTest="Never")
|
only_unfinished=True)
|
||||||
|
self.mock_client.commit_pending_bios_changes.assert_called_once_with(
|
||||||
|
True)
|
||||||
|
|
||||||
|
def test_commit_config_fail(self):
|
||||||
|
self.mock_client.list_jobs.return_value = []
|
||||||
|
exc = drac_exceptions.BaseClientException('boom')
|
||||||
|
self.mock_client.commit_pending_bios_changes.side_effect = exc
|
||||||
|
|
||||||
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,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
task.node = self.node
|
self.assertRaises(exception.DracOperationError,
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
task.driver.vendor.commit_bios_config, task)
|
||||||
bios.set_config, task,
|
|
||||||
AssetTag=tag)
|
|
||||||
|
|
||||||
def test_set_config_string_nomatch(self, client):
|
self.mock_client.list_jobs.assert_called_once_with(
|
||||||
_mock_pywsman_responses(client, _set_config())
|
only_unfinished=True)
|
||||||
tag = unquote('%80')
|
self.mock_client.commit_pending_bios_changes.assert_called_once_with(
|
||||||
|
False)
|
||||||
|
|
||||||
|
def test_abandon_config(self):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
task.node = self.node
|
task.driver.vendor.abandon_bios_config(task)
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
|
||||||
bios.set_config, task,
|
self.mock_client.abandon_pending_bios_changes.assert_called_once_with()
|
||||||
AssetTag=tag)
|
|
||||||
|
def test_abandon_config_fail(self):
|
||||||
|
exc = drac_exceptions.BaseClientException('boom')
|
||||||
|
self.mock_client.abandon_pending_bios_changes.side_effect = exc
|
||||||
|
|
||||||
def test_set_config_integer_toosmall(self, client):
|
|
||||||
_mock_pywsman_responses(client, _set_config())
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
task.node = self.node
|
self.assertRaises(exception.DracOperationError,
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
task.driver.vendor.abandon_bios_config, task)
|
||||||
bios.set_config, task,
|
|
||||||
AcPwrRcvryUserDelay=0)
|
|
||||||
|
|
||||||
def test_set_config_integer_toobig(self, client):
|
self.mock_client.abandon_pending_bios_changes.assert_called_once_with()
|
||||||
_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)
|
|
||||||
mock_cfcj.assert_called_once_with(self.node)
|
|
||||||
mock_ccj.assert_called_once_with(self.node, False)
|
|
||||||
|
|
||||||
@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_with_reboot(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, reboot=True)
|
|
||||||
mock_cfcj.assert_called_once_with(self.node)
|
|
||||||
mock_ccj.assert_called_once_with(self.node, True)
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
@ -24,17 +24,12 @@ import mock
|
|||||||
import ironic.common.boot_devices
|
import ironic.common.boot_devices
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.drivers.modules.drac import client as drac_client
|
|
||||||
from ironic.drivers.modules.drac import common as drac_common
|
from ironic.drivers.modules.drac import common as drac_common
|
||||||
from ironic.drivers.modules.drac import job as drac_job
|
from ironic.drivers.modules.drac import job as drac_job
|
||||||
from ironic.drivers.modules.drac import management as drac_mgmt
|
from ironic.drivers.modules.drac import management as drac_mgmt
|
||||||
from ironic.drivers.modules.drac import resource_uris
|
|
||||||
from ironic.tests.unit.conductor import mgr_utils
|
from ironic.tests.unit.conductor import mgr_utils
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
|
|
||||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
|
||||||
as mock_specs
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_drac_info()
|
INFO_DICT = db_utils.get_test_drac_info()
|
||||||
@ -271,119 +266,3 @@ class DracManagementTestCase(db_base.DbTestCase):
|
|||||||
shared=False) as task:
|
shared=False) as task:
|
||||||
self.assertRaises(NotImplementedError,
|
self.assertRaises(NotImplementedError,
|
||||||
task.driver.management.get_sensors_data, task)
|
task.driver.management.get_sensors_data, task)
|
||||||
|
|
||||||
|
|
||||||
# TODO(ifarkas): delete this during BIOS vendor_passthru refactor
|
|
||||||
@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
|
|
||||||
class DracConfigJobMethodsTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(DracConfigJobMethodsTestCase, 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=INFO_DICT)
|
|
||||||
|
|
||||||
def test__check_for_config_job(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'DCIM_LifecycleJob': {'Name': 'fake'}}],
|
|
||||||
resource_uris.DCIM_LifecycleJob)
|
|
||||||
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.enumerate.return_value = mock_xml
|
|
||||||
|
|
||||||
result = drac_mgmt.check_for_config_job(self.node)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
mock_pywsman.enumerate.assert_called_once_with(
|
|
||||||
mock.ANY, mock.ANY, resource_uris.DCIM_LifecycleJob)
|
|
||||||
|
|
||||||
def test__check_for_config_job_already_exist(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'DCIM_LifecycleJob': {'Name': 'BIOS.Setup.1-1',
|
|
||||||
'JobStatus': 'scheduled',
|
|
||||||
'InstanceID': 'fake'}}],
|
|
||||||
resource_uris.DCIM_LifecycleJob)
|
|
||||||
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.enumerate.return_value = mock_xml
|
|
||||||
|
|
||||||
self.assertRaises(exception.DracPendingConfigJobExists,
|
|
||||||
drac_mgmt.check_for_config_job, self.node)
|
|
||||||
mock_pywsman.enumerate.assert_called_once_with(
|
|
||||||
mock.ANY, mock.ANY, resource_uris.DCIM_LifecycleJob)
|
|
||||||
|
|
||||||
def test__check_for_config_job_not_exist(self, mock_client_pywsman):
|
|
||||||
job_statuses = ["Completed", "Completed with Errors", "Failed"]
|
|
||||||
for job_status in job_statuses:
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'DCIM_LifecycleJob': {'Name': 'BIOS.Setup.1-1',
|
|
||||||
'JobStatus': job_status,
|
|
||||||
'InstanceID': 'fake'}}],
|
|
||||||
resource_uris.DCIM_LifecycleJob)
|
|
||||||
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.enumerate.return_value = mock_xml
|
|
||||||
|
|
||||||
try:
|
|
||||||
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):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_CREATED}],
|
|
||||||
resource_uris.DCIM_BIOSService)
|
|
||||||
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
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_with_reboot(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_CREATED}],
|
|
||||||
resource_uris.DCIM_BIOSService)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
mock_pywsman_clientopts = (
|
|
||||||
mock_client_pywsman.ClientOptions.return_value)
|
|
||||||
|
|
||||||
result = drac_mgmt.create_config_job(self.node, reboot=True)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
mock_pywsman_clientopts.add_property.assert_has_calls([
|
|
||||||
mock.call('RebootJobType', '3'),
|
|
||||||
])
|
|
||||||
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):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_ERROR,
|
|
||||||
'Message': 'E_FAKE'}],
|
|
||||||
resource_uris.DCIM_BIOSService)
|
|
||||||
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
|
||||||
drac_mgmt.create_config_job, self.node)
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(
|
|
||||||
mock.ANY, resource_uris.DCIM_BIOSService,
|
|
||||||
'CreateTargetedConfigJob', None)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user