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:
parent
78737b5e28
commit
a870b97799
|
@ -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')
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}
|
|
@ -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>"""
|
|
@ -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)
|
|
@ -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(
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue