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
|
||||
``/etc/ironic/ironic.conf``
|
||||
- Install openwsman-python package
|
||||
- Install python-dracclient package
|
||||
|
||||
AMT
|
||||
----
|
||||
|
@ -14,8 +14,8 @@ python-seamicroclient>=0.4.0
|
||||
UcsSdk==0.8.2.2
|
||||
python-dracclient>=0.0.5
|
||||
|
||||
# The drac and amt driver import a python module called "pywsman", however,
|
||||
# this does not exist on pypi.
|
||||
# The amt driver import a python module called "pywsman", however, this does
|
||||
# not exist on pypi.
|
||||
# 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
|
||||
# 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')
|
||||
|
||||
|
||||
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):
|
||||
_msg_fmt = _("Failed to get sensor data for node %(node)s. "
|
||||
"Error: %(error)s")
|
||||
|
@ -33,11 +33,6 @@ class PXEDracDriver(base.BaseDriver):
|
||||
"""Drac driver using PXE for deploy."""
|
||||
|
||||
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'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
|
@ -178,11 +178,6 @@ class FakeDracDriver(base.BaseDriver):
|
||||
"""Fake Drac driver."""
|
||||
|
||||
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'):
|
||||
raise exception.DriverLoadError(
|
||||
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 import utils
|
||||
|
||||
pywsman = importutils.try_import('pywsman')
|
||||
drac_client = importutils.try_import('dracclient.client')
|
||||
drac_constants = importutils.try_import('dracclient.constants')
|
||||
|
||||
@ -113,24 +112,3 @@ def get_drac_client(node):
|
||||
driver_info['drac_protocol'])
|
||||
|
||||
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.unit.db import base as db_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.modules.amt import utils as test_utils
|
||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||
as mock_specs
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
@ -27,7 +27,7 @@ from ironic.drivers.modules.amt import resource_uris
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_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.modules.amt import utils as test_utils
|
||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||
as mock_specs
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
@ -29,7 +29,7 @@ from ironic.drivers.modules.amt import resource_uris
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_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.modules.amt import utils as test_utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import dracclient.client
|
||||
import mock
|
||||
from testtools.matchers import HasLength
|
||||
|
||||
from ironic.common import exception
|
||||
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)
|
||||
|
||||
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
|
||||
# the optional drivers.modules.drac and drivers.modules.amt module
|
||||
# the optional drivers.modules.amt module
|
||||
pywsman = importutils.try_import('pywsman')
|
||||
if not pywsman:
|
||||
pywsman = mock.MagicMock(spec_set=mock_specs.PYWSMAN_SPEC)
|
||||
sys.modules['pywsman'] = pywsman
|
||||
# Now that the external library has been mocked, if anything had already
|
||||
# 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:
|
||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.amt'])
|
||||
|
||||
# attempt to load the external 'python-dracclient' library, which is required
|
||||
# by the optional drivers.modules.drac module. 'python-dracclient' is going to
|
||||
# be used in the DRAC driver, once we will complete migration from 'pywsman'
|
||||
# by the optional drivers.modules.drac module
|
||||
dracclient = importutils.try_import('dracclient')
|
||||
if not dracclient:
|
||||
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