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:
Imre Farkas 2015-12-09 15:45:48 +01:00
parent 51a73e11c2
commit 47be8011c6
16 changed files with 21 additions and 680 deletions

View File

@ -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
----

View File

@ -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.

View File

@ -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")

View File

@ -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__,

View File

@ -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__,

View File

@ -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())

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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.