DRAC: cleanup after switch to python-dracclient
DRAC specific code from Ironic is moving to its own project, to python-dracclient project. This patch finishes refactoring code in Ironic to use the new library. Change-Id: I6223dbbeb770d773ebdd72277017156cbdb1d035
This commit is contained in:
parent
51a73e11c2
commit
47be8011c6
@ -21,7 +21,7 @@ DRAC with PXE deploy
|
|||||||
|
|
||||||
- Add ``pxe_drac`` to the list of ``enabled_drivers`` in
|
- Add ``pxe_drac`` to the list of ``enabled_drivers`` in
|
||||||
``/etc/ironic/ironic.conf``
|
``/etc/ironic/ironic.conf``
|
||||||
- Install openwsman-python package
|
- Install python-dracclient package
|
||||||
|
|
||||||
AMT
|
AMT
|
||||||
----
|
----
|
||||||
|
@ -14,8 +14,8 @@ python-seamicroclient>=0.4.0
|
|||||||
UcsSdk==0.8.2.2
|
UcsSdk==0.8.2.2
|
||||||
python-dracclient>=0.0.5
|
python-dracclient>=0.0.5
|
||||||
|
|
||||||
# The drac and amt driver import a python module called "pywsman", however,
|
# The amt driver import a python module called "pywsman", however, this does
|
||||||
# this does not exist on pypi.
|
# not exist on pypi.
|
||||||
# It is installed by the openwsman-python (on RH) or python-openwsman (on deb)
|
# It is installed by the openwsman-python (on RH) or python-openwsman (on deb)
|
||||||
# package, from https://github.com/Openwsman/openwsman/blob/master/bindings/python/Makefile.am#L29
|
# package, from https://github.com/Openwsman/openwsman/blob/master/bindings/python/Makefile.am#L29
|
||||||
# There is *also* a "wsman" module on pypi ... but I think that's the wrong one.
|
# There is *also* a "wsman" module on pypi ... but I think that's the wrong one.
|
||||||
|
@ -488,38 +488,6 @@ class DracOperationError(IronicException):
|
|||||||
_msg_fmt = _('DRAC operation failed. Reason: %(error)s')
|
_msg_fmt = _('DRAC operation failed. Reason: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
class DracRequestFailed(IronicException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DracClientError(DracRequestFailed):
|
|
||||||
_msg_fmt = _('DRAC client failed. '
|
|
||||||
'Last error (cURL error code): %(last_error)s, '
|
|
||||||
'fault string: "%(fault_string)s" '
|
|
||||||
'response_code: %(response_code)s')
|
|
||||||
|
|
||||||
|
|
||||||
class DracOperationFailed(DracRequestFailed):
|
|
||||||
_msg_fmt = _('DRAC operation failed. _msg_fmt: %(_msg_fmt)s')
|
|
||||||
|
|
||||||
|
|
||||||
class DracUnexpectedReturnValue(DracRequestFailed):
|
|
||||||
_msg_fmt = _('DRAC operation yielded return value %(actual_return_value)s '
|
|
||||||
'that is neither error nor expected '
|
|
||||||
'%(expected_return_value)s')
|
|
||||||
|
|
||||||
|
|
||||||
class DracPendingConfigJobExists(IronicException):
|
|
||||||
_msg_fmt = _('Another job with ID %(job_id)s is already created '
|
|
||||||
'to configure %(target)s. Wait until existing job '
|
|
||||||
'is completed or is canceled')
|
|
||||||
|
|
||||||
|
|
||||||
class DracInvalidFilterDialect(IronicException):
|
|
||||||
_msg_fmt = _('Invalid filter dialect \'%(invalid_filter)s\'. '
|
|
||||||
'Supported options are %(supported)s')
|
|
||||||
|
|
||||||
|
|
||||||
class FailedToGetSensorData(IronicException):
|
class FailedToGetSensorData(IronicException):
|
||||||
_msg_fmt = _("Failed to get sensor data for node %(node)s. "
|
_msg_fmt = _("Failed to get sensor data for node %(node)s. "
|
||||||
"Error: %(error)s")
|
"Error: %(error)s")
|
||||||
|
@ -33,11 +33,6 @@ class PXEDracDriver(base.BaseDriver):
|
|||||||
"""Drac driver using PXE for deploy."""
|
"""Drac driver using PXE for deploy."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if not importutils.try_import('pywsman'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_('Unable to import pywsman library'))
|
|
||||||
|
|
||||||
if not importutils.try_import('dracclient'):
|
if not importutils.try_import('dracclient'):
|
||||||
raise exception.DriverLoadError(
|
raise exception.DriverLoadError(
|
||||||
driver=self.__class__.__name__,
|
driver=self.__class__.__name__,
|
||||||
|
@ -178,11 +178,6 @@ class FakeDracDriver(base.BaseDriver):
|
|||||||
"""Fake Drac driver."""
|
"""Fake Drac driver."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if not importutils.try_import('pywsman'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_('Unable to import pywsman library'))
|
|
||||||
|
|
||||||
if not importutils.try_import('dracclient'):
|
if not importutils.try_import('dracclient'):
|
||||||
raise exception.DriverLoadError(
|
raise exception.DriverLoadError(
|
||||||
driver=self.__class__.__name__,
|
driver=self.__class__.__name__,
|
||||||
|
@ -1,268 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Wrapper for pywsman.Client
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
from xml.etree import ElementTree
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common.i18n import _
|
|
||||||
from ironic.common.i18n import _LW
|
|
||||||
from ironic.drivers.modules.drac import common as drac_common
|
|
||||||
|
|
||||||
pywsman = importutils.try_import('pywsman')
|
|
||||||
|
|
||||||
opts = [
|
|
||||||
cfg.IntOpt('client_retry_count',
|
|
||||||
default=5,
|
|
||||||
help=_('In case there is a communication failure, the DRAC '
|
|
||||||
'client resends the request as many times as '
|
|
||||||
'defined in this setting.')),
|
|
||||||
cfg.IntOpt('client_retry_delay',
|
|
||||||
default=5,
|
|
||||||
help=_('In case there is a communication failure, the DRAC '
|
|
||||||
'client waits for as many seconds as defined '
|
|
||||||
'in this setting before resending the request.'))
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
opt_group = cfg.OptGroup(name='drac',
|
|
||||||
title='Options for the DRAC driver')
|
|
||||||
CONF.register_group(opt_group)
|
|
||||||
CONF.register_opts(opts, opt_group)
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
_SOAP_ENVELOPE_URI = 'http://www.w3.org/2003/05/soap-envelope'
|
|
||||||
|
|
||||||
# Filter Dialects, see (Section 2.3.1):
|
|
||||||
# http://en.community.dell.com/techcenter/extras/m/white_papers/20439105.aspx
|
|
||||||
_FILTER_DIALECT_MAP = {'cql': 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf',
|
|
||||||
'wql': 'http://schemas.microsoft.com/wbem/wsman/1/WQL'}
|
|
||||||
|
|
||||||
# ReturnValue constants
|
|
||||||
RET_SUCCESS = '0'
|
|
||||||
RET_ERROR = '2'
|
|
||||||
RET_CREATED = '4096'
|
|
||||||
|
|
||||||
|
|
||||||
def get_wsman_client(node):
|
|
||||||
"""Return a DRAC client object.
|
|
||||||
|
|
||||||
Given an ironic node object, this method gives back a
|
|
||||||
Client object which is a wrapper for pywsman.Client.
|
|
||||||
|
|
||||||
:param node: an ironic node object.
|
|
||||||
:returns: a Client object.
|
|
||||||
:raises: InvalidParameterValue if some mandatory information
|
|
||||||
is missing on the node or on invalid inputs.
|
|
||||||
"""
|
|
||||||
driver_info = drac_common.parse_driver_info(node)
|
|
||||||
client = Client(**driver_info)
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def retry_on_empty_response(client, action, *args, **kwargs):
|
|
||||||
"""Wrapper to retry an action on failure."""
|
|
||||||
|
|
||||||
func = getattr(client, action)
|
|
||||||
for i in range(CONF.drac.client_retry_count):
|
|
||||||
response = func(*args, **kwargs)
|
|
||||||
if response:
|
|
||||||
return response
|
|
||||||
else:
|
|
||||||
LOG.warning(_LW('Empty response on calling %(action)s on client. '
|
|
||||||
'Last error (cURL error code): %(last_error)s, '
|
|
||||||
'fault string: "%(fault_string)s" '
|
|
||||||
'response_code: %(response_code)s. '
|
|
||||||
'Retry attempt %(count)d') %
|
|
||||||
{'action': action,
|
|
||||||
'last_error': client.last_error(),
|
|
||||||
'fault_string': client.fault_string(),
|
|
||||||
'response_code': client.response_code(),
|
|
||||||
'count': i + 1})
|
|
||||||
|
|
||||||
time.sleep(CONF.drac.client_retry_delay)
|
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
|
|
||||||
def __init__(self, drac_host, drac_port, drac_path, drac_protocol,
|
|
||||||
drac_username, drac_password):
|
|
||||||
pywsman_client = pywsman.Client(drac_host, drac_port, drac_path,
|
|
||||||
drac_protocol, drac_username,
|
|
||||||
drac_password)
|
|
||||||
# TODO(ifarkas): Add support for CACerts
|
|
||||||
pywsman.wsman_transport_set_verify_peer(pywsman_client, False)
|
|
||||||
pywsman.wsman_transport_set_verify_host(pywsman_client, False)
|
|
||||||
|
|
||||||
self.client = pywsman_client
|
|
||||||
|
|
||||||
def wsman_enumerate(self, resource_uri, filter_query=None,
|
|
||||||
filter_dialect='cql'):
|
|
||||||
"""Enumerates a remote WS-Man class.
|
|
||||||
|
|
||||||
:param resource_uri: URI of the resource.
|
|
||||||
:param filter_query: the query string.
|
|
||||||
:param filter_dialect: the filter dialect. Valid options are:
|
|
||||||
'cql' and 'wql'. Defaults to 'cql'.
|
|
||||||
:raises: DracClientError on an error from pywsman library.
|
|
||||||
:raises: DracInvalidFilterDialect if an invalid filter dialect
|
|
||||||
was specified.
|
|
||||||
:returns: an ElementTree object of the response received.
|
|
||||||
"""
|
|
||||||
options = pywsman.ClientOptions()
|
|
||||||
|
|
||||||
filter_ = None
|
|
||||||
if filter_query is not None:
|
|
||||||
try:
|
|
||||||
filter_dialect = _FILTER_DIALECT_MAP[filter_dialect]
|
|
||||||
except KeyError:
|
|
||||||
valid_opts = ', '.join(_FILTER_DIALECT_MAP)
|
|
||||||
raise exception.DracInvalidFilterDialect(
|
|
||||||
invalid_filter=filter_dialect, supported=valid_opts)
|
|
||||||
|
|
||||||
filter_ = pywsman.Filter()
|
|
||||||
filter_.simple(filter_dialect, filter_query)
|
|
||||||
|
|
||||||
options.set_flags(pywsman.FLAG_ENUMERATION_OPTIMIZATION)
|
|
||||||
options.set_max_elements(100)
|
|
||||||
|
|
||||||
doc = 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
|
|
||||||
insertion_point = final_xml.find(find_query)
|
|
||||||
while doc.context() is not None:
|
|
||||||
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)
|
|
||||||
|
|
||||||
return final_xml
|
|
||||||
|
|
||||||
def wsman_invoke(self, resource_uri, method, selectors=None,
|
|
||||||
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: 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.
|
|
||||||
:returns: an ElementTree object of the response received.
|
|
||||||
"""
|
|
||||||
if selectors is None:
|
|
||||||
selectors = {}
|
|
||||||
|
|
||||||
if properties is None:
|
|
||||||
properties = {}
|
|
||||||
|
|
||||||
options = pywsman.ClientOptions()
|
|
||||||
|
|
||||||
for name, value in selectors.items():
|
|
||||||
options.add_selector(name, value)
|
|
||||||
|
|
||||||
# NOTE(ifarkas): manually constructing the XML doc should be deleted
|
|
||||||
# once pywsman supports passing a list as a property.
|
|
||||||
# For now this is only a fallback method: in case no
|
|
||||||
# list provided, the supported pywsman API will be used.
|
|
||||||
list_included = any([isinstance(prop_item, list) for prop_item
|
|
||||||
in properties.values()])
|
|
||||||
if list_included:
|
|
||||||
xml_doc = pywsman.XmlDoc('%s_INPUT' % method, resource_uri)
|
|
||||||
xml_root = xml_doc.root()
|
|
||||||
|
|
||||||
for name, value in properties.items():
|
|
||||||
if isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
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
|
|
||||||
|
|
||||||
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 == 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:
|
|
||||||
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
|
|
||||||
|
|
||||||
def _get_root(self, doc):
|
|
||||||
if doc is None or doc.root() is None:
|
|
||||||
raise exception.DracClientError(
|
|
||||||
last_error=self.client.last_error(),
|
|
||||||
fault_string=self.client.fault_string(),
|
|
||||||
response_code=self.client.response_code())
|
|
||||||
root = doc.root()
|
|
||||||
return ElementTree.fromstring(root.string())
|
|
@ -21,7 +21,6 @@ from ironic.common import exception
|
|||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
|
|
||||||
pywsman = importutils.try_import('pywsman')
|
|
||||||
drac_client = importutils.try_import('dracclient.client')
|
drac_client = importutils.try_import('dracclient.client')
|
||||||
drac_constants = importutils.try_import('dracclient.constants')
|
drac_constants = importutils.try_import('dracclient.constants')
|
||||||
|
|
||||||
@ -113,24 +112,3 @@ def get_drac_client(node):
|
|||||||
driver_info['drac_protocol'])
|
driver_info['drac_protocol'])
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
def find_xml(doc, item, namespace, find_all=False):
|
|
||||||
"""Find the first or all elements in an ElementTree object.
|
|
||||||
|
|
||||||
:param doc: the element tree object.
|
|
||||||
:param item: the element name.
|
|
||||||
:param namespace: the namespace of the element.
|
|
||||||
:param find_all: Boolean value, if True find all elements, if False
|
|
||||||
find only the first one. Defaults to False.
|
|
||||||
:returns: if find_all is False the element object will be returned
|
|
||||||
if found, None if not found. If find_all is True a list of
|
|
||||||
element objects will be returned or an empty list if no
|
|
||||||
elements were found.
|
|
||||||
|
|
||||||
"""
|
|
||||||
query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace,
|
|
||||||
'item': item})
|
|
||||||
if find_all:
|
|
||||||
return doc.findall(query)
|
|
||||||
return doc.find(query)
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Resource URIs and helper functions for the classes implemented by the DRAC
|
|
||||||
WS-Man API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
DCIM_ComputerSystem = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2'
|
|
||||||
'/DCIM_ComputerSystem')
|
|
||||||
|
|
||||||
DCIM_BootSourceSetting = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
|
|
||||||
'DCIM_BootSourceSetting')
|
|
||||||
|
|
||||||
DCIM_BootConfigSetting = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/'
|
|
||||||
'DCIM_BootConfigSetting')
|
|
||||||
|
|
||||||
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'
|
|
@ -27,7 +27,7 @@ from ironic.drivers.modules.amt import resource_uris
|
|||||||
from ironic.tests import base
|
from ironic.tests import base
|
||||||
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.modules.amt import utils as test_utils
|
||||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||||
as 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
|
||||||
|
@ -27,7 +27,7 @@ from ironic.drivers.modules.amt 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.modules.amt import utils as test_utils
|
||||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||||
as 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
|
||||||
|
@ -29,7 +29,7 @@ from ironic.drivers.modules.amt 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.modules.amt 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
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_amt_info()
|
INFO_DICT = db_utils.get_test_amt_info()
|
||||||
|
@ -1,256 +0,0 @@
|
|||||||
#
|
|
||||||
# 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 client wrapper.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
from xml.etree import ElementTree
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.drivers.modules.drac import client as drac_client
|
|
||||||
from ironic.tests import base
|
|
||||||
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
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_drac_info()
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
|
|
||||||
class DracClientTestCase(base.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(DracClientTestCase, self).setUp()
|
|
||||||
self.resource_uri = 'http://foo/wsman'
|
|
||||||
|
|
||||||
def test_wsman_enumerate(self, mock_client_pywsman):
|
|
||||||
mock_xml = test_utils.mock_wsman_root('<test></test>')
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.enumerate.return_value = mock_xml
|
|
||||||
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_enumerate(self.resource_uri)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_options.set_flags.assert_called_once_with(
|
|
||||||
mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION)
|
|
||||||
mock_options.set_max_elements.assert_called_once_with(100)
|
|
||||||
mock_pywsman_client.enumerate.assert_called_once_with(
|
|
||||||
mock_options, None, self.resource_uri)
|
|
||||||
mock_xml.context.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
|
||||||
def test_wsman_enumerate_retry(self, mock_client_pywsman):
|
|
||||||
mock_xml = test_utils.mock_wsman_root('<test></test>')
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.enumerate.side_effect = [None, mock_xml]
|
|
||||||
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_enumerate(self.resource_uri)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_options.set_flags.assert_called_once_with(
|
|
||||||
mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION)
|
|
||||||
mock_options.set_max_elements.assert_called_once_with(100)
|
|
||||||
mock_pywsman_client.enumerate.assert_has_calls([
|
|
||||||
mock.call(mock_options, None, self.resource_uri),
|
|
||||||
mock.call(mock_options, None, self.resource_uri)
|
|
||||||
])
|
|
||||||
mock_xml.context.assert_called_once_with()
|
|
||||||
|
|
||||||
def test_wsman_enumerate_with_additional_pull(self, mock_client_pywsman):
|
|
||||||
mock_root = mock.Mock(spec=['string'])
|
|
||||||
mock_root.string.side_effect = [
|
|
||||||
test_utils.build_soap_xml([{'item1': 'test1'}]),
|
|
||||||
test_utils.build_soap_xml([{'item2': 'test2'}])
|
|
||||||
]
|
|
||||||
mock_xml = mock.Mock(spec=['context', 'root'])
|
|
||||||
mock_xml.root.return_value = mock_root
|
|
||||||
mock_xml.context.side_effect = [42, 42, None]
|
|
||||||
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.enumerate.return_value = mock_xml
|
|
||||||
mock_pywsman_client.pull.return_value = mock_xml
|
|
||||||
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
result = client.wsman_enumerate(self.resource_uri)
|
|
||||||
|
|
||||||
# assert the XML was merged
|
|
||||||
result_string = ElementTree.tostring(result)
|
|
||||||
self.assertIn(b'<item1>test1</item1>', result_string)
|
|
||||||
self.assertIn(b'<item2>test2</item2>', result_string)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_options.set_flags.assert_called_once_with(
|
|
||||||
mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION)
|
|
||||||
mock_options.set_max_elements.assert_called_once_with(100)
|
|
||||||
mock_pywsman_client.enumerate.assert_called_once_with(
|
|
||||||
mock_options, None, self.resource_uri)
|
|
||||||
|
|
||||||
def test_wsman_enumerate_filter_query(self, mock_client_pywsman):
|
|
||||||
mock_xml = test_utils.mock_wsman_root('<test></test>')
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.enumerate.return_value = mock_xml
|
|
||||||
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
filter_query = 'SELECT * FROM foo'
|
|
||||||
client.wsman_enumerate(self.resource_uri, filter_query=filter_query)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_filter = mock_client_pywsman.Filter.return_value
|
|
||||||
mock_filter.simple.assert_called_once_with(mock.ANY, filter_query)
|
|
||||||
mock_pywsman_client.enumerate.assert_called_once_with(
|
|
||||||
mock_options, mock_filter, self.resource_uri)
|
|
||||||
mock_xml.context.assert_called_once_with()
|
|
||||||
|
|
||||||
def test_wsman_enumerate_invalid_filter_dialect(self, mock_client_pywsman):
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
self.assertRaises(exception.DracInvalidFilterDialect,
|
|
||||||
client.wsman_enumerate, self.resource_uri,
|
|
||||||
filter_query='foo',
|
|
||||||
filter_dialect='invalid')
|
|
||||||
|
|
||||||
def test_wsman_invoke(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_invoke(self.resource_uri, method_name)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_pywsman_client.invoke.assert_called_once_with(
|
|
||||||
mock_options, self.resource_uri, method_name, None)
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
|
||||||
def test_wsman_invoke_retry(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.side_effect = [None, mock_xml]
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_invoke(self.resource_uri, method_name)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_pywsman_client.invoke.assert_has_calls([
|
|
||||||
mock.call(mock_options, self.resource_uri, method_name, None),
|
|
||||||
mock.call(mock_options, self.resource_uri, method_name, None)
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_wsman_invoke_with_selectors(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
selectors = {'foo': 'bar'}
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_invoke(self.resource_uri, method_name,
|
|
||||||
selectors=selectors)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_pywsman_client.invoke.assert_called_once_with(
|
|
||||||
mock_options, self.resource_uri, method_name, None)
|
|
||||||
mock_options.add_selector.assert_called_once_with('foo', 'bar')
|
|
||||||
|
|
||||||
def test_wsman_invoke_with_properties(self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
properties = {'foo': 'bar'}
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_invoke(self.resource_uri, method_name,
|
|
||||||
properties=properties)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_pywsman_client.invoke.assert_called_once_with(
|
|
||||||
mock_options, self.resource_uri, method_name, None)
|
|
||||||
mock_options.add_property.assert_called_once_with('foo', 'bar')
|
|
||||||
|
|
||||||
def test_wsman_invoke_with_properties_including_a_list(
|
|
||||||
self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.return_value = mock_xml
|
|
||||||
mock_request_xml = mock_client_pywsman.XmlDoc.return_value
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
properties = {'foo': ['bar', 'baz']}
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
client.wsman_invoke(self.resource_uri, method_name,
|
|
||||||
properties=properties)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_pywsman_client.invoke.assert_called_once_with(
|
|
||||||
mock_options, self.resource_uri, method_name, mock_request_xml)
|
|
||||||
mock_request_xml.root().add.assert_has_calls([
|
|
||||||
mock.call(self.resource_uri, 'foo', 'bar'),
|
|
||||||
mock.call(self.resource_uri, 'foo', 'baz')
|
|
||||||
])
|
|
||||||
self.assertEqual(2, mock_request_xml.root().add.call_count)
|
|
||||||
|
|
||||||
def test_wsman_invoke_receives_error_return_value(
|
|
||||||
self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': drac_client.RET_ERROR,
|
|
||||||
'Message': 'error message'}],
|
|
||||||
self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
self.assertRaises(exception.DracOperationFailed,
|
|
||||||
client.wsman_invoke, self.resource_uri, method_name)
|
|
||||||
|
|
||||||
mock_options = mock_client_pywsman.ClientOptions.return_value
|
|
||||||
mock_pywsman_client.invoke.assert_called_once_with(
|
|
||||||
mock_options, self.resource_uri, method_name, None)
|
|
||||||
|
|
||||||
def test_wsman_invoke_receives_unexpected_return_value(
|
|
||||||
self, mock_client_pywsman):
|
|
||||||
result_xml = test_utils.build_soap_xml(
|
|
||||||
[{'ReturnValue': '42'}], self.resource_uri)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman_client = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman_client.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
method_name = 'method'
|
|
||||||
client = drac_client.Client(**INFO_DICT)
|
|
||||||
self.assertRaises(exception.DracUnexpectedReturnValue,
|
|
||||||
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(
|
|
||||||
mock_options, self.resource_uri, method_name, None)
|
|
@ -15,11 +15,8 @@
|
|||||||
Test class for common methods used by DRAC modules.
|
Test class for common methods used by DRAC modules.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.etree import ElementTree
|
|
||||||
|
|
||||||
import dracclient.client
|
import dracclient.client
|
||||||
import mock
|
import mock
|
||||||
from testtools.matchers import HasLength
|
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.drivers.modules.drac import common as drac_common
|
from ironic.drivers.modules.drac import common as drac_common
|
||||||
@ -124,35 +121,3 @@ class DracCommonMethodsTestCase(db_base.DbTestCase):
|
|||||||
drac_common.get_drac_client(node)
|
drac_common.get_drac_client(node)
|
||||||
|
|
||||||
self.assertEqual(mock_dracclient.mock_calls, [expected_call])
|
self.assertEqual(mock_dracclient.mock_calls, [expected_call])
|
||||||
|
|
||||||
def test_find_xml(self):
|
|
||||||
namespace = 'http://fake'
|
|
||||||
value = 'fake_value'
|
|
||||||
test_doc = ElementTree.fromstring("""<Envelope xmlns:ns1="%(ns)s">
|
|
||||||
<Body>
|
|
||||||
<ns1:test_element>%(value)s</ns1:test_element>
|
|
||||||
</Body>
|
|
||||||
</Envelope>""" % {'ns': namespace, 'value': value})
|
|
||||||
|
|
||||||
result = drac_common.find_xml(test_doc, 'test_element', namespace)
|
|
||||||
self.assertEqual(value, result.text)
|
|
||||||
|
|
||||||
def test_find_xml_find_all(self):
|
|
||||||
namespace = 'http://fake'
|
|
||||||
value1 = 'fake_value1'
|
|
||||||
value2 = 'fake_value2'
|
|
||||||
test_doc = ElementTree.fromstring("""<Envelope xmlns:ns1="%(ns)s">
|
|
||||||
<Body>
|
|
||||||
<ns1:test_element>%(value1)s</ns1:test_element>
|
|
||||||
<ns1:cat>meow</ns1:cat>
|
|
||||||
<ns1:test_element>%(value2)s</ns1:test_element>
|
|
||||||
<ns1:dog>bark</ns1:dog>
|
|
||||||
</Body>
|
|
||||||
</Envelope>""" % {'ns': namespace, 'value1': value1,
|
|
||||||
'value2': value2})
|
|
||||||
|
|
||||||
result = drac_common.find_xml(test_doc, 'test_element',
|
|
||||||
namespace, find_all=True)
|
|
||||||
self.assertThat(result, HasLength(2))
|
|
||||||
result_text = [v.text for v in result]
|
|
||||||
self.assertEqual(sorted([value1, value2]), sorted(result_text))
|
|
||||||
|
@ -127,21 +127,18 @@ if not oneview_client:
|
|||||||
|
|
||||||
|
|
||||||
# attempt to load the external 'pywsman' library, which is required by
|
# attempt to load the external 'pywsman' library, which is required by
|
||||||
# the optional drivers.modules.drac and drivers.modules.amt module
|
# the optional drivers.modules.amt module
|
||||||
pywsman = importutils.try_import('pywsman')
|
pywsman = importutils.try_import('pywsman')
|
||||||
if not pywsman:
|
if not pywsman:
|
||||||
pywsman = mock.MagicMock(spec_set=mock_specs.PYWSMAN_SPEC)
|
pywsman = mock.MagicMock(spec_set=mock_specs.PYWSMAN_SPEC)
|
||||||
sys.modules['pywsman'] = pywsman
|
sys.modules['pywsman'] = pywsman
|
||||||
# Now that the external library has been mocked, if anything had already
|
# Now that the external library has been mocked, if anything had already
|
||||||
# loaded any of the drivers, reload them.
|
# loaded any of the drivers, reload them.
|
||||||
if 'ironic.drivers.modules.drac' in sys.modules:
|
|
||||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.drac'])
|
|
||||||
if 'ironic.drivers.modules.amt' in sys.modules:
|
if 'ironic.drivers.modules.amt' in sys.modules:
|
||||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.amt'])
|
six.moves.reload_module(sys.modules['ironic.drivers.modules.amt'])
|
||||||
|
|
||||||
# attempt to load the external 'python-dracclient' library, which is required
|
# attempt to load the external 'python-dracclient' library, which is required
|
||||||
# by the optional drivers.modules.drac module. 'python-dracclient' is going to
|
# by the optional drivers.modules.drac module
|
||||||
# be used in the DRAC driver, once we will complete migration from 'pywsman'
|
|
||||||
dracclient = importutils.try_import('dracclient')
|
dracclient = importutils.try_import('dracclient')
|
||||||
if not dracclient:
|
if not dracclient:
|
||||||
dracclient = mock.MagicMock(spec_set=mock_specs.DRACCLIENT_SPEC)
|
dracclient = mock.MagicMock(spec_set=mock_specs.DRACCLIENT_SPEC)
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- DRAC driver migrated from ``pywsman`` to ``python-dracclient`` fixing
|
||||||
|
the driver lockup issue caused by the python interpreter not handling
|
||||||
|
signals when execution handed to the c library.
|
||||||
|
- Fixes an issue with setting the boot device multiple times without a reboot
|
||||||
|
in the DRAC driver by setting the boot device only before power management
|
||||||
|
operations.
|
||||||
|
upgrade:
|
||||||
|
- Dependency for DRAC driver changed from ``pywsman`` to
|
||||||
|
``python-dracclient``. Exceptions thrown by the driver and return values of
|
||||||
|
the ``set_bios_config``, ``commit_bios_config`` and ``abandon_bios_config``
|
||||||
|
methods changed on the vendor-passthru interface.
|
Loading…
Reference in New Issue
Block a user