diff options
author | Ramakrishnan G <rameshg87@gmail.com> | 2014-05-31 01:51:38 +0530 |
---|---|---|
committer | Anusha Ramineni <anusha.iiitm@gmail.com> | 2014-07-17 20:50:53 +0530 |
commit | e59e5cdb1f46555a25cea4160904d42f852092e0 (patch) | |
tree | 36c06ad56866ddd5f101ae8296b3146cb758c588 | |
parent | 1326235151aea7858e52efda58eb01782e382110 (diff) | |
download | ironic-e59e5cdb1f46555a25cea4160904d42f852092e0.tar.gz |
Add IloDriver and its IloPower module
Add a new ironic driver for managing HP Proliant Gen8 servers
using iLO4. This commit introduces the power module for
IloDriver.
Implements: blueprint ironic-ilo-power-driver
Change-Id: I8d521f67fb14a6132626782b05cd490cd42ba476
Co-Authors: Anusha Ramineni<anusha.iiitm@gmail.com>
-rw-r--r-- | etc/ironic/ironic.conf.sample | 26 | ||||
-rw-r--r-- | ironic/common/exception.py | 4 | ||||
-rw-r--r-- | ironic/drivers/ilo.py | 42 | ||||
-rw-r--r-- | ironic/drivers/modules/ilo/__init__.py | 0 | ||||
-rw-r--r-- | ironic/drivers/modules/ilo/common.py | 137 | ||||
-rw-r--r-- | ironic/drivers/modules/ilo/power.py | 205 | ||||
-rw-r--r-- | ironic/tests/db/utils.py | 8 | ||||
-rw-r--r-- | ironic/tests/drivers/ilo/__init__.py | 0 | ||||
-rw-r--r-- | ironic/tests/drivers/ilo/test_common.py | 156 | ||||
-rw-r--r-- | ironic/tests/drivers/ilo/test_power.py | 195 | ||||
-rw-r--r-- | ironic/tests/drivers/third_party_driver_mocks.py | 9 | ||||
-rw-r--r-- | setup.cfg | 1 |
12 files changed, 783 insertions, 0 deletions
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index 53b378dab..f0ffb1877 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -715,6 +715,32 @@ #auth_strategy=keystone +[ilo] + +# +# Options defined in ironic.drivers.modules.ilo.common +# + +# Timeout (in seconds) for iLO operations (integer value) +#client_timeout=60 + +# Port to be used for iLO operations (integer value) +#client_port=443 + + +# +# Options defined in ironic.drivers.modules.ilo.power +# + +# Number of times a power operation needs to be retried +# (integer value) +#power_retry=6 + +# Amount of time in seconds to wait in between power +# operations (integer value) +#power_wait=2 + + [ipmi] # diff --git a/ironic/common/exception.py b/ironic/common/exception.py index f0318f028..bee73f814 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -377,3 +377,7 @@ class ConsoleSubprocessFailed(IronicException): class PasswordFileFailedToCreate(IronicException): message = _("Failed to create the password file. %(error)s") + + +class IloOperationError(IronicException): + message = _("%(operation)s failed, error: %(error)s") diff --git a/ironic/drivers/ilo.py b/ironic/drivers/ilo.py new file mode 100644 index 000000000..65fb8c1f8 --- /dev/null +++ b/ironic/drivers/ilo.py @@ -0,0 +1,42 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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. +""" +iLO Driver for managing HP Proliant Gen8 and above servers. +""" + +from ironic.common import exception +from ironic.drivers import base +from ironic.drivers.modules.ilo import power +from ironic.openstack.common import importutils + + +class IloDriver(base.BaseDriver): + """IloDriver using IloClient interface. + + This driver implements the `core` functionality using + :class:ironic.drivers.modules.ilo.power.IloPower for power management. + """ + + def __init__(self): + + if not importutils.try_import('proliantutils'): + raise exception.DriverLoadError( + driver=self.__class__.__name__, + reason=_("Unable to import proliantutils library")) + + self.power = power.IloPower() + self.deploy = None + self.rescue = None + self.console = None + self.vendor = None diff --git a/ironic/drivers/modules/ilo/__init__.py b/ironic/drivers/modules/ilo/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ironic/drivers/modules/ilo/__init__.py diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py new file mode 100644 index 000000000..f645139ae --- /dev/null +++ b/ironic/drivers/modules/ilo/common.py @@ -0,0 +1,137 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + +""" +Common functionalities shared between different iLO modules. +""" + +from oslo.config import cfg + +from ironic.common import exception +from ironic.openstack.common import importutils +from ironic.openstack.common import log as logging + +ilo_client = importutils.try_import('proliantutils.ilo.ribcl') + + +STANDARD_LICENSE = 1 +ESSENTIALS_LICENSE = 2 +ADVANCED_LICENSE = 3 + + +opts = [ + cfg.IntOpt('client_timeout', + default=60, + help='Timeout (in seconds) for iLO operations'), + cfg.IntOpt('client_port', + default=443, + help='Port to be used for iLO operations'), +] + +CONF = cfg.CONF +CONF.register_opts(opts, group='ilo') + +LOG = logging.getLogger(__name__) + + +def parse_driver_info(node): + """Gets the driver specific Node deployment info. + + This method validates whether the 'driver_info' property of the + supplied node contains the required information for this driver. + + :param node: an ironic node object. + :returns: a dict containing information from driver_info + and default values. + :raises: InvalidParameterValue if some mandatory information + is missing on the node or on invalid inputs. + """ + info = node.driver_info + d_info = {} + + error_msgs = [] + for param in ('ilo_address', 'ilo_username', 'ilo_password'): + try: + d_info[param] = info[param] + except KeyError: + error_msgs.append(_("'%s' not supplied to IloDriver.") % param) + + for param in ('client_port', 'client_timeout'): + value = info.get(param, CONF.ilo.get(param)) + try: + value = int(value) + except ValueError: + error_msgs.append(_("'%s' is not an integer.") % param) + continue + d_info[param] = value + + if error_msgs: + msg = (_("The following errors were encountered while parsing " + "driver_info:\n%s") % "\n".join(error_msgs)) + raise exception.InvalidParameterValue(msg) + + return d_info + + +def get_ilo_object(node): + """Gets an IloClient object from proliantutils library. + + Given an ironic node object, this method gives back a IloClient object + to do operations on the iLO. + + :param node: an ironic node object. + :returns: an IloClient object. + :raises: InvalidParameterValue if some mandatory information + is missing on the node or on invalid inputs. + """ + driver_info = parse_driver_info(node) + ilo_object = ilo_client.IloClient(driver_info['ilo_address'], + driver_info['ilo_username'], + driver_info['ilo_password'], + driver_info['client_timeout'], + driver_info['client_port']) + return ilo_object + + +def get_ilo_license(node): + """Gives the current installed license on the node. + + Given an ironic node object, this method queries the iLO + for currently installed license and returns it back. + + :param node: an ironic node object. + :returns: a constant defined in this module which + refers to the current license installed on the node. + :raises: InvalidParameterValue if some mandatory information + is missing on the node or on invalid inputs. + :raises: IloOperationError if it failed to retrieve the + installed licenses from the iLO. + """ + # Get the ilo client object, and then the license from the iLO + ilo_object = get_ilo_object(node) + try: + license_info = ilo_object.get_all_licenses() + except ilo_client.IloError as ilo_exception: + raise exception.IloOperationError(operation=_('iLO license check'), + error=str(ilo_exception)) + + # Check the license to see if the given license exists + current_license_type = license_info['LICENSE_TYPE'] + + if current_license_type.endswith("Advanced"): + return ADVANCED_LICENSE + elif current_license_type.endswith("Essentials"): + return ESSENTIALS_LICENSE + else: + return STANDARD_LICENSE diff --git a/ironic/drivers/modules/ilo/power.py b/ironic/drivers/modules/ilo/power.py new file mode 100644 index 000000000..cabf51193 --- /dev/null +++ b/ironic/drivers/modules/ilo/power.py @@ -0,0 +1,205 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + +""" +iLO Power Driver +""" + +from oslo.config import cfg + +from ironic.common import exception +from ironic.common import states +from ironic.conductor import task_manager +from ironic.drivers import base +from ironic.drivers.modules.ilo import common as ilo_common +from ironic.openstack.common import importutils +from ironic.openstack.common import log as logging +from ironic.openstack.common import loopingcall + +ilo_client = importutils.try_import('proliantutils.ilo.ribcl') + + +opts = [ + cfg.IntOpt('power_retry', + default=6, + help='Number of times a power operation needs to be retried'), + cfg.IntOpt('power_wait', + default=2, + help='Amount of time in seconds to wait in between power ' + 'operations'), +] + +CONF = cfg.CONF +CONF.register_opts(opts, group='ilo') + +LOG = logging.getLogger(__name__) + + +def _get_power_state(node): + """Returns the current power state of the node. + + :param node: The node. + :returns: power state, one of :mod: `ironic.common.states`. + :raises: InvalidParameterValue if required iLO credentials are missing. + :raises: IloOperationError on an error from IloClient library. + """ + + ilo_object = ilo_common.get_ilo_object(node) + + # Check the current power state. + try: + power_status = ilo_object.get_host_power_status() + + except ilo_client.IloError as ilo_exception: + LOG.error(_("iLO get_power_state failed for node %(node_id)s with " + "error: %(error)s."), + {'node_id': node.uuid, 'error': ilo_exception}) + operation = _('iLO get_power_status') + raise exception.IloOperationError(operation=operation, + error=ilo_exception) + + if power_status == "ON": + return states.POWER_ON + elif power_status == "OFF": + return states.POWER_OFF + else: + return states.ERROR + + +def _wait_for_state_change(node, target_state): + """Wait for the power state change to get reflected.""" + state = [None] + retries = [0] + + def _wait(state): + + state[0] = _get_power_state(node) + + # NOTE(rameshg87): For reboot operations, initially the state + # will be same as the final state. So defer the check for one retry. + if retries[0] != 0 and state[0] == target_state: + raise loopingcall.LoopingCallDone() + + if retries[0] > CONF.ilo.power_retry: + state[0] = states.ERROR + raise loopingcall.LoopingCallDone() + + retries[0] += 1 + + # Start a timer and wait for the operation to complete. + timer = loopingcall.FixedIntervalLoopingCall(_wait, state) + timer.start(interval=CONF.ilo.power_wait).wait() + + return state[0] + + +def _set_power_state(node, target_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: InvalidParameterValue if an invalid power state was specified. + :raises: IloOperationError on an error from IloClient library. + :raises: PowerStateFailure if the power couldn't be set to target_state. + """ + + ilo_object = ilo_common.get_ilo_object(node) + + # Trigger the operation based on the target state. + try: + if target_state == states.POWER_OFF: + ilo_object.hold_pwr_btn() + elif target_state == states.POWER_ON: + ilo_object.set_host_power('ON') + elif target_state == states.REBOOT: + ilo_object.reset_server() + target_state = states.POWER_ON + else: + msg = _("_set_power_state called with invalid power state " + "'%s'") % target_state + raise exception.InvalidParameterValue(msg) + + except ilo_client.IloError as ilo_exception: + LOG.error(_("iLO set_power_state failed to set state to %(tstate)s " + " for node %(node_id)s with error: %(error)s"), + {'tstate': target_state, 'node_id': node.uuid, + 'error': ilo_exception}) + operation = _('iLO set_power_state') + raise exception.IloOperationError(operation=operation, + error=ilo_exception) + + # Wait till the state change gets reflected. + state = _wait_for_state_change(node, target_state) + + if state != target_state: + timeout = (CONF.ilo.power_wait) * (CONF.ilo.power_retry) + LOG.error(_("iLO failed to change state to %(tstate)s " + "within %(timeout)s sec"), + {'tstate': target_state, 'timeout': timeout}) + raise exception.PowerStateFailure(pstate=target_state) + + +class IloPower(base.PowerInterface): + + def validate(self, task): + """Check if node.driver_info contains the required iLO credentials. + + :param task: a TaskManager instance. + :param node: Single node object. + :raises: InvalidParameterValue if required iLO credentials are missing. + """ + ilo_common.parse_driver_info(task.node) + + def get_power_state(self, task): + """Gets the current power state. + + :param task: a TaskManager instance. + :param node: The Node. + :returns: one of :mod:`ironic.common.states` POWER_OFF, + POWER_ON or ERROR. + :raises: InvalidParameterValue if required iLO credentials are missing. + :raises: IloOperationError on an error from IloClient library. + """ + return _get_power_state(task.node) + + @task_manager.require_exclusive_lock + def set_power_state(self, task, power_state): + """Turn the current power state on or off. + + :param task: a TaskManager instance. + :param node: The Node. + :param power_state: The desired power state POWER_ON,POWER_OFF or + REBOOT from :mod:`ironic.common.states`. + :raises: InvalidParameterValue if an invalid power state was specified. + :raises: IloOperationError on an error from IloClient library. + :raises: PowerStateFailure if the power couldn't be set to power_state. + """ + _set_power_state(task.node, power_state) + + @task_manager.require_exclusive_lock + def reboot(self, task): + """Reboot the node + + :param task: a TaskManager instance. + :param node: The Node. + :raises: PowerStateFailure if the final state of the node is not + POWER_ON. + :raises: IloOperationError on an error from IloClient library. + """ + node = task.node + current_pstate = _get_power_state(node) + if current_pstate == states.POWER_ON: + _set_power_state(node, states.REBOOT) + elif current_pstate == states.POWER_OFF: + _set_power_state(node, states.POWER_ON) diff --git a/ironic/tests/db/utils.py b/ironic/tests/db/utils.py index 4e6773440..0f014c4a0 100644 --- a/ironic/tests/db/utils.py +++ b/ironic/tests/db/utils.py @@ -70,6 +70,14 @@ def get_test_seamicro_info(): } +def get_test_ilo_info(): + return { + "ilo_address": "1.2.3.4", + "ilo_username": "admin", + "ilo_password": "fake", + } + + def get_test_node(**kw): properties = { "cpu_arch": "x86_64", diff --git a/ironic/tests/drivers/ilo/__init__.py b/ironic/tests/drivers/ilo/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ironic/tests/drivers/ilo/__init__.py diff --git a/ironic/tests/drivers/ilo/test_common.py b/ironic/tests/drivers/ilo/test_common.py new file mode 100644 index 000000000..75db20116 --- /dev/null +++ b/ironic/tests/drivers/ilo/test_common.py @@ -0,0 +1,156 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# 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 common methods used by iLO modules.""" + +import mock + +from oslo.config import cfg + +from ironic.common import exception +from ironic.db import api as dbapi +from ironic.drivers.modules.ilo import common as ilo_common +from ironic.openstack.common import context +from ironic.openstack.common import importutils +from ironic.tests import base +from ironic.tests.db import utils as db_utils +from ironic.tests.objects import utils as obj_utils + +ilo_client = importutils.try_import('proliantutils.ilo.ribcl') + + +INFO_DICT = db_utils.get_test_ilo_info() +CONF = cfg.CONF + + +class IloCommonMethodsTestCase(base.TestCase): + + def setUp(self): + super(IloCommonMethodsTestCase, self).setUp() + self.dbapi = dbapi.get_instance() + self.context = context.get_admin_context() + + def test_parse_driver_info(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + info = ilo_common.parse_driver_info(node) + + self.assertIsNotNone(info.get('ilo_address')) + self.assertIsNotNone(info.get('ilo_username')) + self.assertIsNotNone(info.get('ilo_password')) + self.assertIsNotNone(info.get('client_timeout')) + self.assertIsNotNone(info.get('client_port')) + + def test_parse_driver_info_missing_address(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + del node.driver_info['ilo_address'] + self.assertRaises(exception.InvalidParameterValue, + ilo_common.parse_driver_info, node) + + def test_parse_driver_info_missing_username(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + del node.driver_info['ilo_username'] + self.assertRaises(exception.InvalidParameterValue, + ilo_common.parse_driver_info, node) + + def test_parse_driver_info_missing_password(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + del node.driver_info['ilo_password'] + self.assertRaises(exception.InvalidParameterValue, + ilo_common.parse_driver_info, node) + + def test_parse_driver_info_invalid_timeout(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + node.driver_info['client_timeout'] = 'qwe' + self.assertRaises(exception.InvalidParameterValue, + ilo_common.parse_driver_info, node) + + def test_parse_driver_info_invalid_port(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + node.driver_info['client_port'] = 'qwe' + self.assertRaises(exception.InvalidParameterValue, + ilo_common.parse_driver_info, node) + + def test_parse_driver_info_missing_multiple_params(self): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + del node.driver_info['ilo_password'] + node.driver_info['client_port'] = 'qwe' + try: + ilo_common.parse_driver_info(node) + self.fail("parse_driver_info did not throw exception.") + except exception.InvalidParameterValue as e: + self.assertIn('ilo_password', str(e)) + self.assertIn('client_port', str(e)) + + @mock.patch.object(ilo_common, 'ilo_client') + def test_get_ilo_object(self, ilo_client_mock): + info = INFO_DICT + info['client_timeout'] = 60 + info['client_port'] = 443 + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + ilo_client_mock.IloClient.return_value = 'ilo_object' + returned_ilo_object = ilo_common.get_ilo_object(node) + ilo_client_mock.IloClient.assert_called_with( + INFO_DICT['ilo_address'], + INFO_DICT['ilo_username'], + INFO_DICT['ilo_password'], + info['client_timeout'], + info['client_port']) + self.assertEqual('ilo_object', returned_ilo_object) + + @mock.patch.object(ilo_common, 'ilo_client') + def test_get_ilo_license(self, ilo_client_mock): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + ilo_advanced_license = {'LICENSE_TYPE': 'iLO 3 Advanced'} + ilo_standard_license = {'LICENSE_TYPE': 'iLO 3'} + + ilo_mock_object = ilo_client_mock.IloClient.return_value + ilo_mock_object.get_all_licenses.return_value = ilo_advanced_license + + license = ilo_common.get_ilo_license(node) + self.assertEqual(license, ilo_common.ADVANCED_LICENSE) + + ilo_mock_object.get_all_licenses.return_value = ilo_standard_license + license = ilo_common.get_ilo_license(node) + self.assertEqual(license, ilo_common.STANDARD_LICENSE) + + @mock.patch.object(ilo_common, 'ilo_client') + def test_get_ilo_license_fail(self, ilo_client_mock): + node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=INFO_DICT) + ilo_client_mock.IloError = Exception + ilo_mock_object = ilo_client_mock.IloClient.return_value + ilo_mock_object.get_all_licenses.side_effect = [Exception()] + self.assertRaises(exception.IloOperationError, + ilo_common.get_ilo_license, + node) diff --git a/ironic/tests/drivers/ilo/test_power.py b/ironic/tests/drivers/ilo/test_power.py new file mode 100644 index 000000000..2e3e2dabf --- /dev/null +++ b/ironic/tests/drivers/ilo/test_power.py @@ -0,0 +1,195 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# 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 IloPower module.""" + +import mock +from oslo.config import cfg + +from ironic.common import exception +from ironic.common import states +from ironic.conductor import task_manager +from ironic.db import api as dbapi +from ironic.drivers.modules.ilo import common as ilo_common +from ironic.drivers.modules.ilo import power as ilo_power +from ironic.openstack.common import context +from ironic.openstack.common import importutils +from ironic.tests import base +from ironic.tests.conductor import utils as mgr_utils +from ironic.tests.db import utils as db_utils +from ironic.tests.objects import utils as obj_utils + +ilo_client = importutils.try_import('proliantutils.ilo.ribcl') + +INFO_DICT = db_utils.get_test_ilo_info() +CONF = cfg.CONF + + +@mock.patch.object(ilo_common, 'ilo_client') +@mock.patch.object(ilo_power, 'ilo_client') +class IloPowerInternalMethodsTestCase(base.TestCase): + + def setUp(self): + super(IloPowerInternalMethodsTestCase, self).setUp() + driver_info = INFO_DICT + mgr_utils.mock_the_extension_manager(driver="ilo") + n = db_utils.get_test_node( + driver='ilo', + driver_info=driver_info, + instance_uuid='instance_uuid_123') + self.dbapi = dbapi.get_instance() + self.node = self.dbapi.create_node(n) + self.context = context.get_admin_context() + CONF.set_override('power_retry', 2, 'ilo') + CONF.set_override('power_wait', 0, 'ilo') + + def test__get_power_state(self, power_ilo_client_mock, + common_ilo_client_mock): + ilo_mock_object = common_ilo_client_mock.IloClient.return_value + ilo_mock_object.get_host_power_status.return_value = 'ON' + + self.assertEqual( + states.POWER_ON, ilo_power._get_power_state(self.node)) + + ilo_mock_object.get_host_power_status.return_value = 'OFF' + self.assertEqual( + states.POWER_OFF, ilo_power._get_power_state(self.node)) + + ilo_mock_object.get_host_power_status.return_value = 'ERROR' + self.assertEqual(states.ERROR, ilo_power._get_power_state(self.node)) + + def test__get_power_state_fail(self, power_ilo_client_mock, + common_ilo_client_mock): + power_ilo_client_mock.IloError = Exception + ilo_mock_object = common_ilo_client_mock.IloClient.return_value + ilo_mock_object.get_host_power_status.side_effect = [Exception()] + + self.assertRaises(exception.IloOperationError, + ilo_power._get_power_state, + self.node) + ilo_mock_object.get_host_power_status.assert_called_once_with() + + def test__set_power_state_invalid_state(self, power_ilo_client_mock, + common_ilo_client_mock): + power_ilo_client_mock.IloError = Exception + self.assertRaises(exception.IloOperationError, + ilo_power._set_power_state, + self.node, + states.ERROR) + + def test__set_power_state_reboot_fail(self, power_ilo_client_mock, + common_ilo_client_mock): + power_ilo_client_mock.IloError = Exception + ilo_mock_object = common_ilo_client_mock.IloClient.return_value + ilo_mock_object.reset_server.side_effect = Exception() + + self.assertRaises(exception.IloOperationError, + ilo_power._set_power_state, + self.node, + states.REBOOT) + ilo_mock_object.reset_server.assert_called_once_with() + + def test__set_power_state_reboot_ok(self, power_ilo_client_mock, + common_ilo_client_mock): + power_ilo_client_mock.IloError = Exception + ilo_mock_object = common_ilo_client_mock.IloClient.return_value + ilo_mock_object.get_host_power_status.side_effect = ['ON', 'OFF', 'ON'] + + ilo_power._set_power_state(self.node, states.REBOOT) + + ilo_mock_object.reset_server.assert_called_once_with() + + def test__set_power_state_off_fail(self, power_ilo_client_mock, + common_ilo_client_mock): + power_ilo_client_mock.IloError = Exception + ilo_mock_object = common_ilo_client_mock.IloClient.return_value + ilo_mock_object.get_host_power_status.return_value = 'ON' + + self.assertRaises(exception.PowerStateFailure, + ilo_power._set_power_state, + self.node, + states.POWER_OFF) + + ilo_mock_object.get_host_power_status.assert_called_with() + ilo_mock_object.hold_pwr_btn.assert_called_once_with() + + def test__set_power_state_on_ok(self, power_ilo_client_mock, + common_ilo_client_mock): + power_ilo_client_mock.IloError = Exception + ilo_mock_object = common_ilo_client_mock.IloClient.return_value + ilo_mock_object.get_host_power_status.side_effect = ['OFF', 'ON'] + + target_state = states.POWER_ON + ilo_power._set_power_state(self.node, target_state) + ilo_mock_object.get_host_power_status.assert_called_with() + ilo_mock_object.set_host_power.assert_called_once_with('ON') + + +class IloPowerTestCase(base.TestCase): + + def setUp(self): + self.context = context.get_admin_context() + super(IloPowerTestCase, self).setUp() + driver_info = INFO_DICT + mgr_utils.mock_the_extension_manager(driver="ilo") + self.dbapi = dbapi.get_instance() + self.node = obj_utils.create_test_node(self.context, + driver='ilo', + driver_info=driver_info) + + @mock.patch.object(ilo_common, 'parse_driver_info') + def test_validate(self, mock_drvinfo): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.driver.power.validate(task) + mock_drvinfo.assert_once_called_with(task.node) + + @mock.patch.object(ilo_common, 'parse_driver_info') + def test_validate_fail(self, mock_drvinfo): + side_effect = exception.InvalidParameterValue("Invalid Input") + mock_drvinfo.side_effect = side_effect + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertRaises(exception.InvalidParameterValue, + task.driver.power.validate, + task) + + @mock.patch.object(ilo_power, '_get_power_state') + def test_get_power_state(self, mock_get_power): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + mock_get_power.return_value = states.POWER_ON + self.assertEqual(states.POWER_ON, + task.driver.power.get_power_state(task)) + mock_get_power.assert_called_once_with(task.node) + + @mock.patch.object(ilo_power, '_set_power_state') + def test_set_power_state(self, mock_set_power): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + mock_set_power.return_value = states.POWER_ON + task.driver.power.set_power_state(task, states.POWER_ON) + mock_set_power.assert_called_once_with(task.node, states.POWER_ON) + + @mock.patch.object(ilo_power, '_set_power_state') + @mock.patch.object(ilo_power, '_get_power_state') + def test_reboot(self, mock_get_power, mock_set_power): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + mock_get_power.return_value = states.POWER_ON + mock_set_power.return_value = states.POWER_ON + task.driver.power.reboot(task) + mock_get_power.assert_called_once_with(task.node) + mock_set_power.assert_called_once_with(task.node, states.REBOOT) diff --git a/ironic/tests/drivers/third_party_driver_mocks.py b/ironic/tests/drivers/third_party_driver_mocks.py index 09f4f1495..076681ba1 100644 --- a/ironic/tests/drivers/third_party_driver_mocks.py +++ b/ironic/tests/drivers/third_party_driver_mocks.py @@ -23,6 +23,7 @@ Any external library required by a third-party driver should be mocked here. Current list of mocked libraries: seamicroclient ipminative + proliantutils """ import sys @@ -74,3 +75,11 @@ if not pyghmi: if 'ironic.drivers.modules.ipminative' in sys.modules: reload(sys.modules['ironic.drivers.modules.ipminative']) + +proliantutils = importutils.try_import('proliantutils') +if not proliantutils: + mock_proliant_utils = mock.MagicMock() + sys.modules['proliantutils'] = mock_proliant_utils + +if 'ironic.drivers.ilo' in sys.modules: + reload(sys.modules['ironic.drivers.ilo']) @@ -40,6 +40,7 @@ ironic.drivers = pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver pxe_seamicro = ironic.drivers.pxe:PXEAndSeaMicroDriver + ilo = ironic.drivers.ilo:IloDriver [pbr] autodoc_index_modules = True |