From 0ced09b8324510c17f238e863318539807a93f1d Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Thu, 22 Oct 2015 15:01:17 +0200 Subject: [PATCH] DRAC: switch to python-dracclient on power interface DRAC specific code from Ironic is moving to its own project, to python-dracclient project. This patch starts using the new library. Change-Id: I68251fc2b5dd169bb22bbbe38fe9eea16c5ac806 Partial-Bug: #1454492 Depends-On: Iab9d9f7e4e25e3d3fdec9b28fe49a7226e68c9ff --- driver-requirements.txt | 1 + ironic/common/exception.py | 4 + ironic/drivers/drac.py | 6 + ironic/drivers/fake.py | 5 + ironic/drivers/modules/drac/common.py | 26 +++ ironic/drivers/modules/drac/power.py | 116 +++++------ .../unit/drivers/modules/drac/test_common.py | 23 +++ .../unit/drivers/modules/drac/test_power.py | 191 ++++++------------ .../drivers/third_party_driver_mock_specs.py | 17 ++ .../unit/drivers/third_party_driver_mocks.py | 25 +++ 10 files changed, 227 insertions(+), 187 deletions(-) diff --git a/driver-requirements.txt b/driver-requirements.txt index a455af2c01..686414e40b 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -12,6 +12,7 @@ python-oneviewclient<2.1.0,>=2.0.2 python-scciclient>=0.3.0 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. diff --git a/ironic/common/exception.py b/ironic/common/exception.py index bebf500696..00ce23a573 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -484,6 +484,10 @@ class IloOperationNotSupported(IronicException): _msg_fmt = _("%(operation)s not supported. error: %(error)s") +class DracOperationError(IronicException): + _msg_fmt = _('DRAC operation failed. Reason: %(error)s') + + class DracRequestFailed(IronicException): pass diff --git a/ironic/drivers/drac.py b/ironic/drivers/drac.py index d41e1e13aa..3447790a88 100644 --- a/ironic/drivers/drac.py +++ b/ironic/drivers/drac.py @@ -10,6 +10,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + """ DRAC Driver for remote system management using Dell Remote Access Card. """ @@ -37,6 +38,11 @@ class PXEDracDriver(base.BaseDriver): driver=self.__class__.__name__, reason=_('Unable to import pywsman library')) + if not importutils.try_import('dracclient'): + raise exception.DriverLoadError( + driver=self.__class__.__name__, + reason=_('Unable to import python-dracclient library')) + self.power = power.DracPower() self.boot = pxe.PXEBoot() self.deploy = iscsi_deploy.ISCSIDeploy() diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py index 619918eee9..4c7bfe4363 100644 --- a/ironic/drivers/fake.py +++ b/ironic/drivers/fake.py @@ -182,6 +182,11 @@ class FakeDracDriver(base.BaseDriver): driver=self.__class__.__name__, reason=_('Unable to import pywsman library')) + if not importutils.try_import('dracclient'): + raise exception.DriverLoadError( + driver=self.__class__.__name__, + reason=_('Unable to import python-dracclient library')) + self.power = drac_power.DracPower() self.deploy = fake.FakeDeploy() self.management = drac_mgmt.DracManagement() diff --git a/ironic/drivers/modules/drac/common.py b/ironic/drivers/modules/drac/common.py index d75de26b6f..9a0a0f071d 100644 --- a/ironic/drivers/modules/drac/common.py +++ b/ironic/drivers/modules/drac/common.py @@ -22,6 +22,9 @@ 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') + REQUIRED_PROPERTIES = { 'drac_host': _('IP address or hostname of the DRAC card. Required.'), @@ -74,6 +77,10 @@ def parse_driver_info(node): try: parsed_driver_info['drac_protocol'] = str( driver_info.get('drac_protocol', 'https')) + + if parsed_driver_info['drac_protocol'] not in ['http', 'https']: + error_msgs.append(_("'drac_protocol' must be either 'http' or " + "'https'.")) except UnicodeEncodeError: error_msgs.append(_("'drac_protocol' contains non-ASCII symbol.")) @@ -89,6 +96,25 @@ def parse_driver_info(node): return parsed_driver_info +def get_drac_client(node): + """Returns a DRACClient object from python-dracclient library. + + :param node: an ironic node object. + :returns: a DRACClient object. + :raises: InvalidParameterValue if mandatory information is missing on the + node or on invalid input. + """ + driver_info = parse_driver_info(node) + client = drac_client.DRACClient(driver_info['drac_host'], + driver_info['drac_username'], + driver_info['drac_password'], + driver_info['drac_port'], + driver_info['drac_path'], + 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. diff --git a/ironic/drivers/modules/drac/power.py b/ironic/drivers/modules/drac/power.py index c59986fdac..1977e1192d 100644 --- a/ironic/drivers/modules/drac/power.py +++ b/ironic/drivers/modules/drac/power.py @@ -12,11 +12,10 @@ # under the License. """ -DRAC Power Driver using the Base Server Profile +DRAC power interface """ from oslo_log import log as logging -from oslo_utils import excutils from oslo_utils import importutils from ironic.common import exception @@ -24,80 +23,74 @@ from ironic.common.i18n import _LE from ironic.common import states from ironic.conductor import task_manager from ironic.drivers import base -from ironic.drivers.modules.drac import client as drac_client from ironic.drivers.modules.drac import common as drac_common -from ironic.drivers.modules.drac import resource_uris -pywsman = importutils.try_import('pywsman') +drac_constants = importutils.try_import('dracclient.constants') +drac_exceptions = importutils.try_import('dracclient.exceptions') LOG = logging.getLogger(__name__) -POWER_STATES = { - '2': states.POWER_ON, - '3': states.POWER_OFF, - '11': states.REBOOT, -} +if drac_constants: + POWER_STATES = { + drac_constants.POWER_ON: states.POWER_ON, + drac_constants.POWER_OFF: states.POWER_OFF, + drac_constants.REBOOT: states.REBOOT + } -REVERSE_POWER_STATES = dict((v, k) for (k, v) in POWER_STATES.items()) + REVERSE_POWER_STATES = dict((v, k) for (k, v) in POWER_STATES.items()) def _get_power_state(node): - """Returns the current power state of the node + """Returns the current power state of the node. - :param node: The node. - :returns: power state, one of :mod: `ironic.common.states`. - :raises: DracClientError if the client received unexpected response. + :param node: an ironic node object. + :returns: the power state, one of :mod:`ironic.common.states`. :raises: InvalidParameterValue if required DRAC credentials are missing. + :raises: DracOperationError on an error from python-dracclient """ - client = drac_client.get_wsman_client(node) - filter_query = ('select EnabledState,ElementName from DCIM_ComputerSystem ' - 'where Name="srv:system"') + client = drac_common.get_drac_client(node) + try: - doc = client.wsman_enumerate(resource_uris.DCIM_ComputerSystem, - filter_query=filter_query) - except exception.DracClientError as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to get power state for node ' - '%(node_uuid)s. Reason: %(error)s.'), - {'node_uuid': node.uuid, 'error': exc}) + drac_power_state = client.get_power_state() + except drac_exceptions.BaseClientException as exc: + LOG.error(_LE('DRAC driver failed to get power state for node ' + '%(node_uuid)s. Reason: %(error)s.'), + {'node_uuid': node.uuid, 'error': exc}) + raise exception.DracOperationError(error=exc) - enabled_state = drac_common.find_xml(doc, 'EnabledState', - resource_uris.DCIM_ComputerSystem) - return POWER_STATES[enabled_state.text] + return POWER_STATES[drac_power_state] -def _set_power_state(node, target_state): +def _set_power_state(node, power_state): """Turns the server power on/off or do a reboot. :param node: an ironic node object. - :param target_state: target state of the node. - :raises: DracClientError if the client received unexpected response. - :raises: InvalidParameterValue if an invalid power state was specified. + :param power_state: a power state from :mod:`ironic.common.states`. + :raises: InvalidParameterValue if required DRAC credentials are missing. + :raises: DracOperationError on an error from python-dracclient """ - client = drac_client.get_wsman_client(node) - selectors = {'CreationClassName': 'DCIM_ComputerSystem', - 'Name': 'srv:system'} - properties = {'RequestedState': REVERSE_POWER_STATES[target_state]} + client = drac_common.get_drac_client(node) + target_power_state = REVERSE_POWER_STATES[power_state] try: - client.wsman_invoke(resource_uris.DCIM_ComputerSystem, - 'RequestStateChange', selectors, properties) - except exception.DracRequestFailed as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to set power state for node ' - '%(node_uuid)s to %(target_power_state)s. ' - 'Reason: %(error)s.'), - {'node_uuid': node.uuid, - 'target_power_state': target_state, - 'error': exc}) + client.set_power_state(target_power_state) + except drac_exceptions.BaseClientException as exc: + LOG.error(_LE('DRAC driver failed to set power state for node ' + '%(node_uuid)s to %(power_state)s. ' + 'Reason: %(error)s.'), + {'node_uuid': node.uuid, + 'power_state': power_state, + 'error': exc}) + raise exception.DracOperationError(error=exc) class DracPower(base.PowerInterface): """Interface for power-related actions.""" def get_properties(self): + """Return the properties of the interface.""" return drac_common.COMMON_PROPERTIES def validate(self, task): @@ -114,39 +107,36 @@ class DracPower(base.PowerInterface): return drac_common.parse_driver_info(task.node) def get_power_state(self, task): - """Return the power state of the task's node. + """Return the power state of the node. :param task: a TaskManager instance containing the node to act on. - :returns: a power state. One of :mod:`ironic.common.states`. - :raises: DracClientError if the client received unexpected response. + :returns: the power state, one of :mod:`ironic.common.states`. + :raises: InvalidParameterValue if required DRAC credentials are + missing. + :raises: DracOperationError on an error from python-dracclient. """ return _get_power_state(task.node) @task_manager.require_exclusive_lock def set_power_state(self, task, power_state): - """Set the power state of the task's node. + """Set the power state of the node. :param task: a TaskManager instance containing the node to act on. - :param power_state: Any power state from :mod:`ironic.common.states`. - :raises: DracClientError if the client received unexpected response. - :raises: DracOperationFailed if the client received response with an - error message. - :raises: DracUnexpectedReturnValue if the client received a response - with unexpected return value. - + :param power_state: a power state from :mod:`ironic.common.states`. + :raises: InvalidParameterValue if required DRAC credentials are + missing. + :raises: DracOperationError on an error from python-dracclient. """ _set_power_state(task.node, power_state) @task_manager.require_exclusive_lock def reboot(self, task): - """Perform a hard reboot of the task's node. + """Perform a reboot of the task's node. :param task: a TaskManager instance containing the node to act on. - :raises: DracClientError if the client received unexpected response. - :raises: DracOperationFailed if the client received response with an - error message. - :raises: DracUnexpectedReturnValue if the client received a response - with unexpected return value. + :raises: InvalidParameterValue if required DRAC credentials are + missing. + :raises: DracOperationError on an error from python-dracclient. """ current_power_state = _get_power_state(task.node) diff --git a/ironic/tests/unit/drivers/modules/drac/test_common.py b/ironic/tests/unit/drivers/modules/drac/test_common.py index 2f2b7b7d44..0e18a51ae9 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_common.py +++ b/ironic/tests/unit/drivers/modules/drac/test_common.py @@ -17,6 +17,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 @@ -86,6 +88,15 @@ class DracCommonMethodsTestCase(db_base.DbTestCase): info = drac_common.parse_driver_info(node) self.assertEqual('https', info.get('drac_protocol')) + def test_parse_driver_info_invalid_protocol(self): + node = obj_utils.create_test_node(self.context, + driver='fake_drac', + driver_info=INFO_DICT) + node.driver_info['drac_protocol'] = 'foo' + + self.assertRaises(exception.InvalidParameterValue, + drac_common.parse_driver_info, node) + def test_parse_driver_info_missing_username(self): node = obj_utils.create_test_node(self.context, driver='fake_drac', @@ -102,6 +113,18 @@ class DracCommonMethodsTestCase(db_base.DbTestCase): self.assertRaises(exception.InvalidParameterValue, drac_common.parse_driver_info, node) + @mock.patch.object(dracclient.client, 'DRACClient', autospec=True) + def test_get_drac_client(self, mock_dracclient): + expected_call = mock.call('1.2.3.4', 'admin', 'fake', 443, '/wsman', + 'https') + node = obj_utils.create_test_node(self.context, + driver='fake_drac', + driver_info=INFO_DICT) + + 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' diff --git a/ironic/tests/unit/drivers/modules/drac/test_power.py b/ironic/tests/unit/drivers/modules/drac/test_power.py index e77a5a725d..496cab5e10 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_power.py +++ b/ironic/tests/unit/drivers/modules/drac/test_power.py @@ -12,164 +12,107 @@ # under the License. """ -Test class for DRAC Power Driver +Test class for DRAC power interface """ +from dracclient import constants as drac_constants +from dracclient import exceptions as drac_exceptions import mock from ironic.common import exception from ironic.common import states from ironic.conductor import task_manager -from ironic.drivers.modules.drac import client as drac_client from ironic.drivers.modules.drac import common as drac_common from ironic.drivers.modules.drac import power as drac_power -from ironic.drivers.modules.drac import resource_uris from ironic.tests.unit.conductor import mgr_utils from ironic.tests.unit.db 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 +from ironic.tests.unit.objects import utils as obj_utils INFO_DICT = db_utils.get_test_drac_info() -@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC) -@mock.patch.object(drac_power, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC) -class DracPowerInternalMethodsTestCase(base.DbTestCase): - - def setUp(self): - super(DracPowerInternalMethodsTestCase, self).setUp() - driver_info = INFO_DICT - self.node = db_utils.create_test_node( - driver='fake_drac', - driver_info=driver_info, - instance_uuid='instance_uuid_123') - - def test__get_power_state(self, mock_power_pywsman, mock_client_pywsman): - result_xml = test_utils.build_soap_xml( - [{'EnabledState': '2'}], resource_uris.DCIM_ComputerSystem) - mock_xml = test_utils.mock_wsman_root(result_xml) - mock_pywsman_client = mock_client_pywsman.Client.return_value - mock_pywsman_client.enumerate.return_value = mock_xml - - self.assertEqual(states.POWER_ON, - drac_power._get_power_state(self.node)) - - mock_pywsman_client.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_ComputerSystem) - - def test__set_power_state(self, mock_power_pywsman, mock_client_pywsman): - result_xml = test_utils.build_soap_xml( - [{'ReturnValue': drac_client.RET_SUCCESS}], - resource_uris.DCIM_ComputerSystem) - 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_pywsman_clientopts = ( - mock_client_pywsman.ClientOptions.return_value) - - drac_power._set_power_state(self.node, states.POWER_ON) - - mock_pywsman_clientopts.add_selector.assert_has_calls([ - mock.call('CreationClassName', 'DCIM_ComputerSystem'), - mock.call('Name', 'srv:system') - ], any_order=True) - mock_pywsman_clientopts.add_property.assert_called_once_with( - 'RequestedState', '2') - - mock_pywsman_client.invoke.assert_called_once_with( - mock.ANY, resource_uris.DCIM_ComputerSystem, - 'RequestStateChange', None) - - def test__set_power_state_fail(self, mock_power_pywsman, - mock_client_pywsman): - result_xml = test_utils.build_soap_xml( - [{'ReturnValue': drac_client.RET_ERROR, - 'Message': 'error message'}], - resource_uris.DCIM_ComputerSystem) - - 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_pywsman_clientopts = ( - mock_client_pywsman.ClientOptions.return_value) - - self.assertRaises(exception.DracOperationFailed, - drac_power._set_power_state, self.node, - states.POWER_ON) - - mock_pywsman_clientopts.add_selector.assert_has_calls([ - mock.call('CreationClassName', 'DCIM_ComputerSystem'), - mock.call('Name', 'srv:system') - ], any_order=True) - mock_pywsman_clientopts.add_property.assert_called_once_with( - 'RequestedState', '2') - - mock_pywsman_client.invoke.assert_called_once_with( - mock.ANY, resource_uris.DCIM_ComputerSystem, - 'RequestStateChange', None) - - +@mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) class DracPowerTestCase(base.DbTestCase): def setUp(self): super(DracPowerTestCase, self).setUp() - driver_info = INFO_DICT - mgr_utils.mock_the_extension_manager(driver="fake_drac") - self.node = db_utils.create_test_node( - driver='fake_drac', - driver_info=driver_info, - instance_uuid='instance_uuid_123') + mgr_utils.mock_the_extension_manager(driver='fake_drac') + self.node = obj_utils.create_test_node(self.context, + driver='fake_drac', + driver_info=INFO_DICT) - def test_get_properties(self): + def test_get_properties(self, mock_get_drac_client): expected = drac_common.COMMON_PROPERTIES driver = drac_power.DracPower() self.assertEqual(expected, driver.get_properties()) - @mock.patch.object(drac_power, '_get_power_state', spec_set=True, - autospec=True) - def test_get_power_state(self, mock_get_power_state): - mock_get_power_state.return_value = states.POWER_ON - driver = drac_power.DracPower() - task = mock.Mock() - task.node.return_value = self.node + def test_get_power_state(self, mock_get_drac_client): + mock_client = mock_get_drac_client.return_value + mock_client.get_power_state.return_value = drac_constants.POWER_ON - self.assertEqual(states.POWER_ON, driver.get_power_state(task)) - mock_get_power_state.assert_called_once_with(task.node) + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + power_state = task.driver.power.get_power_state(task) + + self.assertEqual(states.POWER_ON, power_state) + mock_client.get_power_state.assert_called_once_with() + + def test_get_power_state_fail(self, mock_get_drac_client): + mock_client = mock_get_drac_client.return_value + exc = drac_exceptions.BaseClientException('boom') + mock_client.get_power_state.side_effect = exc + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertRaises(exception.DracOperationError, + task.driver.power.get_power_state, task) + + mock_client.get_power_state.assert_called_once_with() + + def test_set_power_state(self, mock_get_drac_client): + mock_client = mock_get_drac_client.return_value - @mock.patch.object(drac_power, '_set_power_state', spec_set=True, - autospec=True) - def test_set_power_state(self, mock_set_power_state): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - task.driver.power.set_power_state(task, states.POWER_ON) - mock_set_power_state.assert_called_once_with(task.node, - states.POWER_ON) + task.driver.power.set_power_state(task, states.POWER_OFF) + + drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_OFF] + mock_client.set_power_state.assert_called_once_with(drac_power_state) + + def test_set_power_state_fail(self, mock_get_drac_client): + mock_client = mock_get_drac_client.return_value + exc = drac_exceptions.BaseClientException('boom') + mock_client.set_power_state.side_effect = exc + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.DracOperationError, + task.driver.power.set_power_state, task, + states.POWER_OFF) + + drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_OFF] + mock_client.set_power_state.assert_called_once_with(drac_power_state) + + def test_reboot_while_powered_on(self, mock_get_drac_client): + mock_client = mock_get_drac_client.return_value + mock_client.get_power_state.return_value = drac_constants.POWER_ON - @mock.patch.object(drac_power, '_set_power_state', spec_set=True, - autospec=True) - @mock.patch.object(drac_power, '_get_power_state', spec_set=True, - autospec=True) - def test_reboot(self, mock_get_power_state, mock_set_power_state): - mock_get_power_state.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.power.reboot(task) - mock_set_power_state.assert_called_once_with(task.node, - states.REBOOT) - @mock.patch.object(drac_power, '_set_power_state', spec_set=True, - autospec=True) - @mock.patch.object(drac_power, '_get_power_state', spec_set=True, - autospec=True) - def test_reboot_in_power_off(self, mock_get_power_state, - mock_set_power_state): - mock_get_power_state.return_value = states.POWER_OFF + drac_power_state = drac_power.REVERSE_POWER_STATES[states.REBOOT] + mock_client.set_power_state.assert_called_once_with(drac_power_state) + + def test_reboot_while_powered_off(self, mock_get_drac_client): + mock_client = mock_get_drac_client.return_value + mock_client.get_power_state.return_value = drac_constants.POWER_OFF + with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.power.reboot(task) - mock_set_power_state.assert_called_once_with(task.node, - states.POWER_ON) + + drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_ON] + mock_client.set_power_state.assert_called_once_with(drac_power_state) diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py index d1454469ee..16cec49725 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -16,6 +16,23 @@ """This module provides mock 'specs' for third party modules that can be used when needing to mock those third party modules""" +# python-dracclient +DRACCLIENT_SPEC = ( + 'client', + 'constants', + 'exceptions' +) + +DRACCLIENT_CLIENT_MOD_SPEC = ( + 'DRACClient', +) + +DRACCLIENT_CONSTANTS_MOD_SPEC = ( + 'POWER_OFF', + 'POWER_ON', + 'REBOOT' +) + # iboot IBOOT_SPEC = ( 'iBootInterface', diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index 4309e2cb0d..5a698bc241 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -28,6 +28,8 @@ Current list of mocked libraries: - pysnmp - scciclient - oneview_client +- pywsman +- python-dracclient """ import sys @@ -137,6 +139,29 @@ if not pywsman: 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' +dracclient = importutils.try_import('dracclient') +if not dracclient: + dracclient = mock.MagicMock(spec_set=mock_specs.DRACCLIENT_SPEC) + dracclient.client = mock.MagicMock( + spec_set=mock_specs.DRACCLIENT_CLIENT_MOD_SPEC) + dracclient.constants = mock.MagicMock( + spec_set=mock_specs.DRACCLIENT_CONSTANTS_MOD_SPEC, + POWER_OFF=mock.sentinel.POWER_OFF, + POWER_ON=mock.sentinel.POWER_ON, + REBOOT=mock.sentinel.REBOOT) + sys.modules['dracclient'] = dracclient + sys.modules['dracclient.client'] = dracclient.client + sys.modules['dracclient.constants'] = dracclient.constants + sys.modules['dracclient.exceptions'] = dracclient.exceptions + dracclient.exceptions.BaseClientException = type('BaseClientException', + (Exception,), {}) + # 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']) # attempt to load the external 'iboot' library, which is required by # the optional drivers.modules.iboot module