summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRamakrishnan G <rameshg87@gmail.com>2014-05-31 01:51:38 +0530
committerAnusha Ramineni <anusha.iiitm@gmail.com>2014-07-17 20:50:53 +0530
commite59e5cdb1f46555a25cea4160904d42f852092e0 (patch)
tree36c06ad56866ddd5f101ae8296b3146cb758c588
parent1326235151aea7858e52efda58eb01782e382110 (diff)
downloadironic-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.sample26
-rw-r--r--ironic/common/exception.py4
-rw-r--r--ironic/drivers/ilo.py42
-rw-r--r--ironic/drivers/modules/ilo/__init__.py0
-rw-r--r--ironic/drivers/modules/ilo/common.py137
-rw-r--r--ironic/drivers/modules/ilo/power.py205
-rw-r--r--ironic/tests/db/utils.py8
-rw-r--r--ironic/tests/drivers/ilo/__init__.py0
-rw-r--r--ironic/tests/drivers/ilo/test_common.py156
-rw-r--r--ironic/tests/drivers/ilo/test_power.py195
-rw-r--r--ironic/tests/drivers/third_party_driver_mocks.py9
-rw-r--r--setup.cfg1
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'])
diff --git a/setup.cfg b/setup.cfg
index 5d0ba4824..8a9d965a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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