Ipmitool power driver for soft reboot and soft power off

This patch enhances ipmitool power driver to support SOFT_REBOOT
and SOFT_POWER_OFF.

Partial-Bug: #1526226
Change-Id: If01721625c22a578b4311b82104cd895139e3a01
This commit is contained in:
Naohiro Tamura 2015-08-25 17:39:46 +09:00
parent 6e2e7a157c
commit ed6c599083
3 changed files with 349 additions and 39 deletions

View File

@ -471,7 +471,7 @@ def _sleep_time(iter):
return iter ** 2 return iter ** 2
def _set_and_wait(target_state, driver_info): def _set_and_wait(power_action, driver_info, timeout=None):
"""Helper function for DynamicLoopingCall. """Helper function for DynamicLoopingCall.
This method changes the power state and polls the BMC until the desired This method changes the power state and polls the BMC until the desired
@ -483,21 +483,32 @@ def _set_and_wait(target_state, driver_info):
if a driver is concerned, the state should be checked prior to calling this if a driver is concerned, the state should be checked prior to calling this
method. method.
:param target_state: desired power state :param power_action: the action Ironic will perform when changing the
power state of the node.
:param timeout: timeout (in seconds) positive integer (> 0) for any
power state. ``None`` indicates to use default timeout.
:param driver_info: the ipmitool parameters for accessing a node. :param driver_info: the ipmitool parameters for accessing a node.
:returns: one of ironic.common.states :returns: one of ironic.common.states
""" """
if target_state == states.POWER_ON: retry_timeout = timeout or CONF.ipmi.retry_timeout
state_name = "on"
elif target_state == states.POWER_OFF: if power_action == states.POWER_ON:
state_name = "off" cmd_name = "on"
target_state = states.POWER_ON
elif power_action == states.POWER_OFF:
cmd_name = "off"
target_state = states.POWER_OFF
elif power_action == states.SOFT_POWER_OFF:
cmd_name = "soft"
target_state = states.POWER_OFF
retry_timeout = timeout or CONF.conductor.soft_power_off_timeout
def _wait(mutable): def _wait(mutable):
try: try:
# Only issue power change command once # Only issue power change command once
if mutable['iter'] < 0: if mutable['iter'] < 0:
_exec_ipmitool(driver_info, "power %s" % state_name) _exec_ipmitool(driver_info, "power %s" % cmd_name)
else: else:
mutable['power'] = _power_status(driver_info) mutable['power'] = _power_status(driver_info)
except (exception.PasswordFileFailedToCreate, except (exception.PasswordFileFailedToCreate,
@ -505,7 +516,7 @@ def _set_and_wait(target_state, driver_info):
exception.IPMIFailure): exception.IPMIFailure):
# Log failures but keep trying # Log failures but keep trying
LOG.warning(_LW("IPMI power %(state)s failed for node %(node)s."), LOG.warning(_LW("IPMI power %(state)s failed for node %(node)s."),
{'state': state_name, 'node': driver_info['uuid']}) {'state': cmd_name, 'node': driver_info['uuid']})
finally: finally:
mutable['iter'] += 1 mutable['iter'] += 1
@ -513,11 +524,11 @@ def _set_and_wait(target_state, driver_info):
raise loopingcall.LoopingCallDone() raise loopingcall.LoopingCallDone()
sleep_time = _sleep_time(mutable['iter']) sleep_time = _sleep_time(mutable['iter'])
if (sleep_time + mutable['total_time']) > CONF.ipmi.retry_timeout: if (sleep_time + mutable['total_time']) > retry_timeout:
# Stop if the next loop would exceed maximum retry_timeout # Stop if the next loop would exceed maximum retry_timeout
LOG.error(_LE('IPMI power %(state)s timed out after ' LOG.error(_LE('IPMI power %(state)s timed out after '
'%(tries)s retries on node %(node_id)s.'), '%(tries)s retries on node %(node_id)s.'),
{'state': state_name, 'tries': mutable['iter'], {'state': cmd_name, 'tries': mutable['iter'],
'node_id': driver_info['uuid']}) 'node_id': driver_info['uuid']})
mutable['power'] = states.ERROR mutable['power'] = states.ERROR
raise loopingcall.LoopingCallDone() raise loopingcall.LoopingCallDone()
@ -534,26 +545,43 @@ def _set_and_wait(target_state, driver_info):
return status['power'] return status['power']
def _power_on(driver_info): def _power_on(driver_info, timeout=None):
"""Turn the power ON for this node. """Turn the power ON for this node.
:param driver_info: the ipmitool parameters for accessing a node. :param driver_info: the ipmitool parameters for accessing a node.
:param timeout: the timeout in seconds (> 0) to wait for the power
action to be completed. ``None`` indicates default timeout".
:returns: one of ironic.common.states POWER_ON or ERROR. :returns: one of ironic.common.states POWER_ON or ERROR.
:raises: IPMIFailure on an error from ipmitool (from _power_status call). :raises: IPMIFailure on an error from ipmitool (from _power_status call).
""" """
return _set_and_wait(states.POWER_ON, driver_info) return _set_and_wait(states.POWER_ON, driver_info, timeout=timeout)
def _power_off(driver_info): def _power_off(driver_info, timeout=None):
"""Turn the power OFF for this node. """Turn the power OFF for this node.
:param driver_info: the ipmitool parameters for accessing a node. :param driver_info: the ipmitool parameters for accessing a node.
:param timeout: the timeout in seconds (> 0) to wait for the power
action to be completed. ``None`` indicates default timeout".
:returns: one of ironic.common.states POWER_OFF or ERROR. :returns: one of ironic.common.states POWER_OFF or ERROR.
:raises: IPMIFailure on an error from ipmitool (from _power_status call). :raises: IPMIFailure on an error from ipmitool (from _power_status call).
""" """
return _set_and_wait(states.POWER_OFF, driver_info) return _set_and_wait(states.POWER_OFF, driver_info, timeout=timeout)
def _soft_power_off(driver_info, timeout=None):
"""Turn the power SOFT OFF for this node.
:param driver_info: the ipmitool parameters for accessing a node.
:param timeout: the timeout in seconds (> 0) to wait for the power
action to be completed. ``None`` indicates default timeout".
:returns: one of ironic.common.states POWER_OFF or ERROR.
:raises: IPMIFailure on an error from ipmitool (from _power_status call).
"""
return _set_and_wait(states.SOFT_POWER_OFF, driver_info, timeout=timeout)
def _power_status(driver_info): def _power_status(driver_info):
@ -793,12 +821,17 @@ class IPMIPower(base.PowerInterface):
@METRICS.timer('IPMIPower.set_power_state') @METRICS.timer('IPMIPower.set_power_state')
@task_manager.require_exclusive_lock @task_manager.require_exclusive_lock
def set_power_state(self, task, pstate): def set_power_state(self, task, power_state, timeout=None):
"""Turn the power on or off. """Turn the power on, off, soft reboot, or soft power off.
:param task: a TaskManager instance containing the node to act on. :param task: a TaskManager instance containing the node to act on.
:param pstate: The desired power state, one of ironic.common.states :param power_state: desired power state.
POWER_ON, POWER_OFF. one of ironic.common.states, POWER_ON, POWER_OFF, SOFT_POWER_OFF,
or SOFT_REBOOT.
:param timeout: timeout (in seconds) positive integer (> 0) for any
power state. The timeout is counted once during power off and once
during power on for reboots. ``None`` indicates that the default
timeout will be used.
:raises: InvalidParameterValue if an invalid power state was specified. :raises: InvalidParameterValue if an invalid power state was specified.
:raises: MissingParameterValue if required ipmi parameters are missing :raises: MissingParameterValue if required ipmi parameters are missing
:raises: PowerStateFailure if the power couldn't be set to pstate. :raises: PowerStateFailure if the power couldn't be set to pstate.
@ -806,41 +839,77 @@ class IPMIPower(base.PowerInterface):
""" """
driver_info = _parse_driver_info(task.node) driver_info = _parse_driver_info(task.node)
if pstate == states.POWER_ON: if power_state == states.POWER_ON:
driver_utils.ensure_next_boot_device(task, driver_info) driver_utils.ensure_next_boot_device(task, driver_info)
state = _power_on(driver_info) target_state = states.POWER_ON
elif pstate == states.POWER_OFF: state = _power_on(driver_info, timeout=timeout)
state = _power_off(driver_info) elif power_state == states.POWER_OFF:
target_state = states.POWER_OFF
state = _power_off(driver_info, timeout=timeout)
elif power_state == states.SOFT_POWER_OFF:
target_state = states.POWER_OFF
state = _soft_power_off(driver_info, timeout=timeout)
elif power_state == states.SOFT_REBOOT:
intermediate_state = _soft_power_off(driver_info, timeout=timeout)
intermediate_target_state = states.POWER_OFF
if intermediate_state != intermediate_target_state:
raise exception.PowerStateFailure(
pstate=(_(
"%(intermediate)s while on %(power_state)s") %
{'intermediate': intermediate_target_state,
'power_state': power_state}))
driver_utils.ensure_next_boot_device(task, driver_info)
target_state = states.POWER_ON
state = _power_on(driver_info, timeout=timeout)
else: else:
raise exception.InvalidParameterValue( raise exception.InvalidParameterValue(
_("set_power_state called " _("set_power_state called "
"with invalid power state %s.") % pstate) "with invalid power state %s.") % power_state)
if state != pstate: if state != target_state:
raise exception.PowerStateFailure(pstate=pstate) raise exception.PowerStateFailure(pstate=target_state)
@METRICS.timer('IPMIPower.reboot') @METRICS.timer('IPMIPower.reboot')
@task_manager.require_exclusive_lock @task_manager.require_exclusive_lock
def reboot(self, task): def reboot(self, task, timeout=None):
"""Cycles the power to the task's node. """Cycles the power to the task's node.
:param task: a TaskManager instance containing the node to act on. :param task: a TaskManager instance containing the node to act on.
:param timeout: timeout (in seconds) positive integer (> 0) for any
power state. The timeout is counted once during power off and once
during power on for reboots. ``None`` indicates that the default
timeout will be used.
:raises: MissingParameterValue if required ipmi parameters are missing. :raises: MissingParameterValue if required ipmi parameters are missing.
:raises: InvalidParameterValue if an invalid power state was specified. :raises: InvalidParameterValue if an invalid power state was specified.
:raises: PowerStateFailure if the final state of the node is not :raises: PowerStateFailure if the final state of the node is not
POWER_ON or the intermediate state of the node is not POWER_OFF. POWER_ON or the intermediate state of the node is not POWER_OFF.
""" """
driver_info = _parse_driver_info(task.node) driver_info = _parse_driver_info(task.node)
intermediate_state = _power_off(driver_info) intermediate_state = _power_off(driver_info, timeout=timeout)
if intermediate_state != states.POWER_OFF: if intermediate_state != states.POWER_OFF:
raise exception.PowerStateFailure(pstate=states.POWER_OFF) raise exception.PowerStateFailure(
pstate=(_(
"%(power_off)s while on %(reboot)s") %
{'power_off': states.POWER_OFF,
'reboot': states.REBOOT}))
driver_utils.ensure_next_boot_device(task, driver_info) driver_utils.ensure_next_boot_device(task, driver_info)
state = _power_on(driver_info) state = _power_on(driver_info, timeout=timeout)
if state != states.POWER_ON: if state != states.POWER_ON:
raise exception.PowerStateFailure(pstate=states.POWER_ON) raise exception.PowerStateFailure(pstate=states.POWER_ON)
def get_supported_power_states(self, task):
"""Get a list of the supported power states.
:param task: A TaskManager instance containing the node to act on.
currently not used.
:returns: A list with the supported power states defined
in :mod:`ironic.common.states`.
"""
return [states.POWER_ON, states.POWER_OFF, states.REBOOT,
states.SOFT_REBOOT, states.SOFT_POWER_OFF]
class IPMIManagement(base.ManagementInterface): class IPMIManagement(base.ManagementInterface):

View File

@ -1257,6 +1257,47 @@ class IPMIToolPrivateMethodTestCase(db_base.DbTestCase):
self.assertEqual(mock_exec.call_args_list, expected) self.assertEqual(mock_exec.call_args_list, expected)
self.assertEqual(states.ERROR, state) self.assertEqual(states.ERROR, state)
@mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
@mock.patch('eventlet.greenthread.sleep', autospec=True)
def test__soft_power_off(self, sleep_mock, mock_exec,
mock_sleep):
def side_effect(driver_info, command):
resp_dict = {"power status": ["Chassis Power is off\n", None],
"power soft": [None, None]}
return resp_dict.get(command, ["Bad\n", None])
mock_exec.side_effect = side_effect
expected = [mock.call(self.info, "power soft"),
mock.call(self.info, "power status")]
state = ipmi._soft_power_off(self.info, timeout=None)
self.assertEqual(mock_exec.call_args_list, expected)
self.assertEqual(states.POWER_OFF, state)
@mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
@mock.patch('eventlet.greenthread.sleep', autospec=True)
def test__soft_power_off_max_retries(self, sleep_mock, mock_exec,
mock_sleep):
def side_effect(driver_info, command):
resp_dict = {"power status": ["Chassis Power is on\n", None],
"power soft": [None, None]}
return resp_dict.get(command, ["Bad\n", None])
mock_exec.side_effect = side_effect
expected = [mock.call(self.info, "power soft"),
mock.call(self.info, "power status"),
mock.call(self.info, "power status")]
state = ipmi._soft_power_off(self.info, timeout=2)
self.assertEqual(mock_exec.call_args_list, expected)
self.assertEqual(states.ERROR, state)
class IPMIToolDriverTestCase(db_base.DbTestCase): class IPMIToolDriverTestCase(db_base.DbTestCase):
@ -1337,7 +1378,22 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
self.driver.power.set_power_state(task, self.driver.power.set_power_state(task,
states.POWER_ON) states.POWER_ON)
mock_on.assert_called_once_with(self.info) mock_on.assert_called_once_with(self.info, timeout=None)
self.assertFalse(mock_off.called)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_power_off', autospec=True)
def test_set_power_on_timeout_ok(self, mock_off, mock_on):
self.config(retry_timeout=0, group='ipmi')
mock_on.return_value = states.POWER_ON
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.driver.power.set_power_state(task,
states.POWER_ON,
timeout=2)
mock_on.assert_called_once_with(self.info, timeout=2)
self.assertFalse(mock_off.called) self.assertFalse(mock_off.called)
@mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True) @mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
@ -1354,7 +1410,25 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
states.POWER_ON) states.POWER_ON)
mock_next_boot.assert_called_once_with(task, self.info) mock_next_boot.assert_called_once_with(task, self.info)
mock_on.assert_called_once_with(self.info) mock_on.assert_called_once_with(self.info, timeout=None)
self.assertFalse(mock_off.called)
@mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_power_off', autospec=True)
def test_set_power_on_with_next_boot_timeout(self, mock_off, mock_on,
mock_next_boot):
self.config(retry_timeout=0, group='ipmi')
mock_on.return_value = states.POWER_ON
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.driver.power.set_power_state(task,
states.POWER_ON,
timeout=2)
mock_next_boot.assert_called_once_with(task, self.info)
mock_on.assert_called_once_with(self.info, timeout=2)
self.assertFalse(mock_off.called) self.assertFalse(mock_off.called)
@mock.patch.object(ipmi, '_power_on', autospec=True) @mock.patch.object(ipmi, '_power_on', autospec=True)
@ -1369,7 +1443,113 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
self.driver.power.set_power_state(task, self.driver.power.set_power_state(task,
states.POWER_OFF) states.POWER_OFF)
mock_off.assert_called_once_with(self.info) mock_off.assert_called_once_with(self.info, timeout=None)
self.assertFalse(mock_on.called)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_power_off', autospec=True)
def test_set_power_off_timeout_ok(self, mock_off, mock_on):
self.config(retry_timeout=0, group='ipmi')
mock_off.return_value = states.POWER_OFF
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.driver.power.set_power_state(task,
states.POWER_OFF,
timeout=2)
mock_off.assert_called_once_with(self.info, timeout=2)
self.assertFalse(mock_on.called)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_soft_power_off', autospec=True)
def test_set_soft_power_off_ok(self, mock_off, mock_on):
self.config(retry_timeout=0, group='ipmi')
mock_off.return_value = states.POWER_OFF
with task_manager.acquire(self.context,
self.node['uuid']) as task:
self.driver.power.set_power_state(task,
states.SOFT_POWER_OFF)
mock_off.assert_called_once_with(self.info, timeout=None)
self.assertFalse(mock_on.called)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_soft_power_off', autospec=True)
def test_set_soft_power_off_timeout_ok(self, mock_off, mock_on):
self.config(retry_timeout=0, group='ipmi')
mock_off.return_value = states.POWER_OFF
with task_manager.acquire(self.context,
self.node['uuid']) as task:
self.driver.power.set_power_state(task,
states.SOFT_POWER_OFF,
timeout=2)
mock_off.assert_called_once_with(self.info, timeout=2)
self.assertFalse(mock_on.called)
@mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_soft_power_off', autospec=True)
def test_set_soft_reboot_ok(self, mock_off, mock_on, mock_next_boot):
self.config(retry_timeout=0, group='ipmi')
mock_off.return_value = states.POWER_OFF
mock_on.return_value = states.POWER_ON
with task_manager.acquire(self.context,
self.node['uuid']) as task:
self.driver.power.set_power_state(task,
states.SOFT_REBOOT)
mock_next_boot.assert_called_once_with(task, self.info)
mock_off.assert_called_once_with(self.info, timeout=None)
mock_on.assert_called_once_with(self.info, timeout=None)
@mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_soft_power_off', autospec=True)
def test_set_soft_reboot_timeout_ok(self, mock_off, mock_on,
mock_next_boot):
self.config(retry_timeout=0, group='ipmi')
mock_off.return_value = states.POWER_OFF
mock_on.return_value = states.POWER_ON
with task_manager.acquire(self.context,
self.node['uuid']) as task:
self.driver.power.set_power_state(task,
states.SOFT_REBOOT,
timeout=2)
mock_next_boot.assert_called_once_with(task, self.info)
mock_off.assert_called_once_with(self.info, timeout=2)
mock_on.assert_called_once_with(self.info, timeout=2)
@mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_soft_power_off', autospec=True)
def test_set_soft_reboot_timeout_fail(self, mock_off, mock_on,
mock_next_boot):
self.config(retry_timeout=0, group='ipmi')
mock_off.return_value = states.POWER_ON
with task_manager.acquire(self.context,
self.node['uuid']) as task:
self.assertRaises(exception.PowerStateFailure,
self.driver.power.set_power_state,
task,
states.SOFT_REBOOT,
timeout=2)
mock_off.assert_called_once_with(self.info, timeout=2)
self.assertFalse(mock_next_boot.called)
self.assertFalse(mock_on.called) self.assertFalse(mock_on.called)
@mock.patch.object(ipmi, '_power_on', autospec=True) @mock.patch.object(ipmi, '_power_on', autospec=True)
@ -1385,7 +1565,24 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
task, task,
states.POWER_ON) states.POWER_ON)
mock_on.assert_called_once_with(self.info) mock_on.assert_called_once_with(self.info, timeout=None)
self.assertFalse(mock_off.called)
@mock.patch.object(ipmi, '_power_on', autospec=True)
@mock.patch.object(ipmi, '_power_off', autospec=True)
def test_set_power_on_timeout_fail(self, mock_off, mock_on):
self.config(retry_timeout=0, group='ipmi')
mock_on.return_value = states.ERROR
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.assertRaises(exception.PowerStateFailure,
self.driver.power.set_power_state,
task,
states.POWER_ON,
timeout=2)
mock_on.assert_called_once_with(self.info, timeout=2)
self.assertFalse(mock_off.called) self.assertFalse(mock_off.called)
def test_set_power_invalid_state(self): def test_set_power_invalid_state(self):
@ -1458,8 +1655,8 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
mock_on.return_value = states.POWER_ON mock_on.return_value = states.POWER_ON
manager.attach_mock(mock_off, 'power_off') manager.attach_mock(mock_off, 'power_off')
manager.attach_mock(mock_on, 'power_on') manager.attach_mock(mock_on, 'power_on')
expected = [mock.call.power_off(self.info), expected = [mock.call.power_off(self.info, timeout=None),
mock.call.power_on(self.info)] mock.call.power_on(self.info, timeout=None)]
with task_manager.acquire(self.context, with task_manager.acquire(self.context,
self.node.uuid) as task: self.node.uuid) as task:
@ -1468,6 +1665,26 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
self.assertEqual(manager.mock_calls, expected) self.assertEqual(manager.mock_calls, expected)
@mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
@mock.patch.object(ipmi, '_power_off', spec_set=types.FunctionType)
@mock.patch.object(ipmi, '_power_on', spec_set=types.FunctionType)
def test_reboot_timeout_ok(self, mock_on, mock_off, mock_next_boot):
manager = mock.MagicMock()
# NOTE(rloo): if autospec is True, then manager.mock_calls is empty
mock_off.return_value = states.POWER_OFF
mock_on.return_value = states.POWER_ON
manager.attach_mock(mock_off, 'power_off')
manager.attach_mock(mock_on, 'power_on')
expected = [mock.call.power_off(self.info, timeout=2),
mock.call.power_on(self.info, timeout=2)]
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.driver.power.reboot(task, timeout=2)
mock_next_boot.assert_called_once_with(task, self.info)
self.assertEqual(manager.mock_calls, expected)
@mock.patch.object(ipmi, '_power_off', spec_set=types.FunctionType) @mock.patch.object(ipmi, '_power_off', spec_set=types.FunctionType)
@mock.patch.object(ipmi, '_power_on', spec_set=types.FunctionType) @mock.patch.object(ipmi, '_power_on', spec_set=types.FunctionType)
def test_reboot_fail_power_off(self, mock_on, mock_off): def test_reboot_fail_power_off(self, mock_on, mock_off):
@ -1476,7 +1693,7 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
mock_off.return_value = states.ERROR mock_off.return_value = states.ERROR
manager.attach_mock(mock_off, 'power_off') manager.attach_mock(mock_off, 'power_off')
manager.attach_mock(mock_on, 'power_on') manager.attach_mock(mock_on, 'power_on')
expected = [mock.call.power_off(self.info)] expected = [mock.call.power_off(self.info, timeout=None)]
with task_manager.acquire(self.context, with task_manager.acquire(self.context,
self.node.uuid) as task: self.node.uuid) as task:
@ -1495,8 +1712,8 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
mock_on.return_value = states.ERROR mock_on.return_value = states.ERROR
manager.attach_mock(mock_off, 'power_off') manager.attach_mock(mock_off, 'power_off')
manager.attach_mock(mock_on, 'power_on') manager.attach_mock(mock_on, 'power_on')
expected = [mock.call.power_off(self.info), expected = [mock.call.power_off(self.info, timeout=None),
mock.call.power_on(self.info)] mock.call.power_on(self.info, timeout=None)]
with task_manager.acquire(self.context, with task_manager.acquire(self.context,
self.node.uuid) as task: self.node.uuid) as task:
@ -1506,6 +1723,26 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
self.assertEqual(manager.mock_calls, expected) self.assertEqual(manager.mock_calls, expected)
@mock.patch.object(ipmi, '_power_off', spec_set=types.FunctionType)
@mock.patch.object(ipmi, '_power_on', spec_set=types.FunctionType)
def test_reboot_timeout_fail(self, mock_on, mock_off):
manager = mock.MagicMock()
# NOTE(rloo): if autospec is True, then manager.mock_calls is empty
mock_off.return_value = states.POWER_OFF
mock_on.return_value = states.ERROR
manager.attach_mock(mock_off, 'power_off')
manager.attach_mock(mock_on, 'power_on')
expected = [mock.call.power_off(self.info, timeout=2),
mock.call.power_on(self.info, timeout=2)]
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.assertRaises(exception.PowerStateFailure,
self.driver.power.reboot,
task, timeout=2)
self.assertEqual(manager.mock_calls, expected)
@mock.patch.object(ipmi, '_parse_driver_info', autospec=True) @mock.patch.object(ipmi, '_parse_driver_info', autospec=True)
def test_vendor_passthru_validate__parse_driver_info_fail(self, info_mock): def test_vendor_passthru_validate__parse_driver_info_fail(self, info_mock):
info_mock.side_effect = exception.InvalidParameterValue("bad") info_mock.side_effect = exception.InvalidParameterValue("bad")

View File

@ -0,0 +1,4 @@
---
features:
- Adds support for ``soft reboot`` and ``soft power off`` to
ipmitool driver.