diff --git a/doc/source/deploy/drivers.rst b/doc/source/deploy/drivers.rst index 370dbbcd0c..db8654699b 100644 --- a/doc/source/deploy/drivers.rst +++ b/doc/source/deploy/drivers.rst @@ -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 ---- diff --git a/driver-requirements.txt b/driver-requirements.txt index 686414e40b..827b8997e6 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -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. diff --git a/ironic/common/exception.py b/ironic/common/exception.py index 00ce23a573..02cb97a1bd 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -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") diff --git a/ironic/drivers/drac.py b/ironic/drivers/drac.py index 3447790a88..a9ff7095e9 100644 --- a/ironic/drivers/drac.py +++ b/ironic/drivers/drac.py @@ -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__, diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py index 98178947fe..877674019b 100644 --- a/ironic/drivers/fake.py +++ b/ironic/drivers/fake.py @@ -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__, diff --git a/ironic/drivers/modules/drac/client.py b/ironic/drivers/modules/drac/client.py deleted file mode 100644 index f868eff41d..0000000000 --- a/ironic/drivers/modules/drac/client.py +++ /dev/null @@ -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()) diff --git a/ironic/drivers/modules/drac/common.py b/ironic/drivers/modules/drac/common.py index 9a0a0f071d..ead3a88724 100644 --- a/ironic/drivers/modules/drac/common.py +++ b/ironic/drivers/modules/drac/common.py @@ -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) diff --git a/ironic/drivers/modules/drac/resource_uris.py b/ironic/drivers/modules/drac/resource_uris.py deleted file mode 100644 index d8e2369c09..0000000000 --- a/ironic/drivers/modules/drac/resource_uris.py +++ /dev/null @@ -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' diff --git a/ironic/tests/unit/drivers/modules/amt/test_common.py b/ironic/tests/unit/drivers/modules/amt/test_common.py index f83ce9c31a..ad70873b07 100644 --- a/ironic/tests/unit/drivers/modules/amt/test_common.py +++ b/ironic/tests/unit/drivers/modules/amt/test_common.py @@ -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 diff --git a/ironic/tests/unit/drivers/modules/amt/test_management.py b/ironic/tests/unit/drivers/modules/amt/test_management.py index f79e26bec5..7b0cfb6a21 100644 --- a/ironic/tests/unit/drivers/modules/amt/test_management.py +++ b/ironic/tests/unit/drivers/modules/amt/test_management.py @@ -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 diff --git a/ironic/tests/unit/drivers/modules/amt/test_power.py b/ironic/tests/unit/drivers/modules/amt/test_power.py index f509bfd442..041ad28879 100644 --- a/ironic/tests/unit/drivers/modules/amt/test_power.py +++ b/ironic/tests/unit/drivers/modules/amt/test_power.py @@ -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() diff --git a/ironic/tests/unit/drivers/modules/drac/utils.py b/ironic/tests/unit/drivers/modules/amt/utils.py similarity index 100% rename from ironic/tests/unit/drivers/modules/drac/utils.py rename to ironic/tests/unit/drivers/modules/amt/utils.py diff --git a/ironic/tests/unit/drivers/modules/drac/test_client.py b/ironic/tests/unit/drivers/modules/drac/test_client.py deleted file mode 100644 index e3755c7270..0000000000 --- a/ironic/tests/unit/drivers/modules/drac/test_client.py +++ /dev/null @@ -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('') - 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('') - 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'test1', result_string) - self.assertIn(b'test2', 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('') - 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) diff --git a/ironic/tests/unit/drivers/modules/drac/test_common.py b/ironic/tests/unit/drivers/modules/drac/test_common.py index 0e18a51ae9..31edb0f2a7 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_common.py +++ b/ironic/tests/unit/drivers/modules/drac/test_common.py @@ -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(""" - - %(value)s - - """ % {'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(""" - - %(value1)s - meow - %(value2)s - bark - - """ % {'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)) diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index 5a698bc241..40f20cbcd3 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -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) diff --git a/releasenotes/notes/drac-migrate-to-dracclient-2bd8a6d1dd3fdc69.yaml b/releasenotes/notes/drac-migrate-to-dracclient-2bd8a6d1dd3fdc69.yaml new file mode 100644 index 0000000000..2f623600ae --- /dev/null +++ b/releasenotes/notes/drac-migrate-to-dracclient-2bd8a6d1dd3fdc69.yaml @@ -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.