Merge "iRMC power driver for soft reboot and soft power off"
This commit is contained in:
commit
eacaa70b0b
@ -1799,6 +1799,9 @@
|
||||
# SNMP security name. Required for version "v3" (string value)
|
||||
#snmp_security = <None>
|
||||
|
||||
# SNMP polling interval in seconds (integer value)
|
||||
#snmp_polling_interval = 10
|
||||
|
||||
|
||||
[ironic_lib]
|
||||
|
||||
|
@ -66,6 +66,9 @@ opts = [
|
||||
help=_('SNMP community. Required for versions "v1" and "v2c"')),
|
||||
cfg.StrOpt('snmp_security',
|
||||
help=_('SNMP security name. Required for version "v3"')),
|
||||
cfg.IntOpt('snmp_polling_interval',
|
||||
default=10,
|
||||
help='SNMP polling interval in seconds'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -15,20 +15,23 @@
|
||||
"""
|
||||
iRMC Power Driver using the Base Server Profile
|
||||
"""
|
||||
|
||||
from ironic_lib import metrics_utils
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _, _LE
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common.i18n import _LI
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules.irmc import boot as irmc_boot
|
||||
from ironic.drivers.modules.irmc import common as irmc_common
|
||||
|
||||
from ironic.drivers.modules import snmp
|
||||
|
||||
scci = importutils.try_import('scciclient.irmc.scci')
|
||||
|
||||
@ -36,37 +39,158 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
|
||||
"""
|
||||
SC2.mib: sc2srvCurrentBootStatus returns status of the current boot
|
||||
"""
|
||||
BOOT_STATUS_OID = "1.3.6.1.4.1.231.2.10.2.2.10.4.1.1.4.1"
|
||||
BOOT_STATUS_VALUE = {
|
||||
'error': 0,
|
||||
'unknown': 1,
|
||||
'off': 2,
|
||||
'no-boot-cpu': 3,
|
||||
'self-test': 4,
|
||||
'setup': 5,
|
||||
'os-boot': 6,
|
||||
'diagnostic-boot': 7,
|
||||
'os-running': 8,
|
||||
'diagnostic-running': 9,
|
||||
'os-shutdown': 10,
|
||||
'diagnostic-shutdown': 11,
|
||||
'reset': 12
|
||||
}
|
||||
BOOT_STATUS = {v: k for k, v in BOOT_STATUS_VALUE.items()}
|
||||
|
||||
if scci:
|
||||
STATES_MAP = {states.POWER_OFF: scci.POWER_OFF,
|
||||
states.POWER_ON: scci.POWER_ON,
|
||||
states.REBOOT: scci.POWER_RESET}
|
||||
states.REBOOT: scci.POWER_RESET,
|
||||
states.SOFT_REBOOT: scci.POWER_SOFT_CYCLE,
|
||||
states.SOFT_POWER_OFF: scci.POWER_SOFT_OFF}
|
||||
|
||||
|
||||
def _set_power_state(task, target_state):
|
||||
"""Turns the server power on/off or do a reboot.
|
||||
def _is_expected_power_state(target_state, boot_status_value):
|
||||
"""Predicate if target power state and boot status values match.
|
||||
|
||||
:param target_state: Target power state.
|
||||
:param boot_status_value: SNMP BOOT_STATUS_VALUE.
|
||||
:returns: True if expected power state, otherwise Flase.
|
||||
"""
|
||||
if (target_state == states.SOFT_POWER_OFF and
|
||||
boot_status_value in (BOOT_STATUS_VALUE['unknown'],
|
||||
BOOT_STATUS_VALUE['off'])):
|
||||
return True
|
||||
elif (target_state == states.SOFT_REBOOT and
|
||||
boot_status_value == BOOT_STATUS_VALUE['os-running']):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _wait_power_state(task, target_state, timeout=None):
|
||||
"""Wait for having changed to the target power state.
|
||||
|
||||
:param task: A TaskManager instance containing the node to act on.
|
||||
:raises: IRMCOperationError if the target state acknowledge failure
|
||||
or SNMP failure.
|
||||
"""
|
||||
node = task.node
|
||||
d_info = irmc_common.parse_driver_info(node)
|
||||
snmp_client = snmp.SNMPClient(d_info['irmc_address'],
|
||||
d_info['irmc_snmp_port'],
|
||||
d_info['irmc_snmp_version'],
|
||||
d_info['irmc_snmp_community'],
|
||||
d_info['irmc_snmp_security'])
|
||||
|
||||
interval = CONF.irmc.snmp_polling_interval
|
||||
retry_timeout_soft = timeout or CONF.conductor.soft_power_off_timeout
|
||||
max_retry = int(retry_timeout_soft / interval)
|
||||
|
||||
def _wait(mutable):
|
||||
mutable['boot_status_value'] = snmp_client.get(BOOT_STATUS_OID)
|
||||
LOG.debug("iRMC SNMP agent of %(node_id)s returned "
|
||||
"boot status value %(bootstatus)s on attempt %(times)s.",
|
||||
{'node_id': node.uuid,
|
||||
'bootstatus': BOOT_STATUS[mutable['boot_status_value']],
|
||||
'times': mutable['times']})
|
||||
|
||||
if _is_expected_power_state(target_state,
|
||||
mutable['boot_status_value']):
|
||||
mutable['state'] = target_state
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
mutable['times'] += 1
|
||||
if mutable['times'] > max_retry:
|
||||
mutable['state'] = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
store = {'state': None, 'times': 0, 'boot_status_value': None}
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait, store)
|
||||
timer.start(interval=interval).wait()
|
||||
|
||||
if store['state'] == target_state:
|
||||
# iRMC acknowledged the target state
|
||||
node.last_error = None
|
||||
node.power_state = (states.POWER_OFF
|
||||
if target_state == states.SOFT_POWER_OFF
|
||||
else states.POWER_ON)
|
||||
node.target_power_state = states.NOSTATE
|
||||
node.save()
|
||||
LOG.info(_LI('iRMC successfully set node %(node_id)s '
|
||||
'power state to %(bootstatus)s.'),
|
||||
{'node_id': node.uuid,
|
||||
'bootstatus': BOOT_STATUS[store['boot_status_value']]})
|
||||
else:
|
||||
# iRMC failed to acknowledge the target state
|
||||
last_error = (_('iRMC returned unexpected boot status value %s') %
|
||||
BOOT_STATUS[store['boot_status_value']])
|
||||
node.last_error = last_error
|
||||
node.power_state = states.ERROR
|
||||
node.target_power_state = states.NOSTATE
|
||||
node.save()
|
||||
LOG.error(_LE('iRMC failed to acknowledge the target state for node '
|
||||
'%(node_id)s. Error: %(last_error)s'),
|
||||
{'node_id': node.uuid, 'last_error': last_error})
|
||||
error = _('unexpected boot status value')
|
||||
raise exception.IRMCOperationError(operation=target_state,
|
||||
error=error)
|
||||
|
||||
|
||||
def _set_power_state(task, target_state, timeout=None):
|
||||
"""Turn the server power on/off or do a reboot.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param target_state: target state of the node.
|
||||
:param timeout: timeout (in seconds) positive integer (> 0) for any
|
||||
power state. ``None`` indicates default timeout.
|
||||
:raises: InvalidParameterValue if an invalid power state was specified.
|
||||
:raises: MissingParameterValue if some mandatory information
|
||||
is missing on the node
|
||||
:raises: IRMCOperationError on an error from SCCI
|
||||
is missing on the node
|
||||
:raises: IRMCOperationError on an error from SCCI or SNMP
|
||||
"""
|
||||
|
||||
node = task.node
|
||||
irmc_client = irmc_common.get_irmc_client(node)
|
||||
|
||||
if target_state in (states.POWER_ON, states.REBOOT):
|
||||
if target_state in (states.POWER_ON, states.REBOOT, states.SOFT_REBOOT):
|
||||
irmc_boot.attach_boot_iso_if_needed(task)
|
||||
|
||||
try:
|
||||
irmc_client(STATES_MAP[target_state])
|
||||
|
||||
if target_state in (states.SOFT_REBOOT, states.SOFT_POWER_OFF):
|
||||
_wait_power_state(task, target_state, timeout=timeout)
|
||||
|
||||
except KeyError:
|
||||
msg = _("_set_power_state called with invalid power state "
|
||||
"'%s'") % target_state
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
except exception.SNMPFailure as snmp_exception:
|
||||
LOG.error(_LE("iRMC failed to acknowledge the target state "
|
||||
"for node %(node_id)s. Error: %(error)s"),
|
||||
{'node_id': node.uuid, 'error': snmp_exception})
|
||||
raise exception.IRMCOperationError(operation=target_state,
|
||||
error=snmp_exception)
|
||||
|
||||
except scci.SCCIClientError as irmc_exception:
|
||||
LOG.error(_LE("iRMC set_power_state failed to set state to %(tstate)s "
|
||||
" for node %(node_id)s with error: %(error)s"),
|
||||
@ -119,29 +243,45 @@ class IRMCPower(base.PowerInterface):
|
||||
|
||||
@METRICS.timer('IRMCPower.set_power_state')
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, power_state):
|
||||
def set_power_state(self, task, power_state, timeout=None):
|
||||
"""Set the power state of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param power_state: Any power state from :mod:`ironic.common.states`.
|
||||
:param timeout: timeout (in seconds) positive integer (> 0) for any
|
||||
power state. ``None`` indicates default timeout.
|
||||
:raises: InvalidParameterValue if an invalid power state was specified.
|
||||
:raises: MissingParameterValue if some mandatory information
|
||||
is missing on the node
|
||||
is missing on the node
|
||||
:raises: IRMCOperationError if failed to set the power state.
|
||||
"""
|
||||
_set_power_state(task, power_state)
|
||||
_set_power_state(task, power_state, timeout=timeout)
|
||||
|
||||
@METRICS.timer('IRMCPower.reboot')
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task):
|
||||
def reboot(self, task, timeout=None):
|
||||
"""Perform a hard reboot of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param timeout: timeout (in seconds) positive integer (> 0) for any
|
||||
power state. ``None`` indicates default timeout.
|
||||
:raises: InvalidParameterValue if an invalid power state was specified.
|
||||
:raises: IRMCOperationError if failed to set the power state.
|
||||
"""
|
||||
current_pstate = self.get_power_state(task)
|
||||
if current_pstate == states.POWER_ON:
|
||||
_set_power_state(task, states.REBOOT)
|
||||
_set_power_state(task, states.REBOOT, timeout=timeout)
|
||||
elif current_pstate == states.POWER_OFF:
|
||||
_set_power_state(task, states.POWER_ON)
|
||||
_set_power_state(task, states.POWER_ON, timeout=timeout)
|
||||
|
||||
@METRICS.timer('IRMCPower.get_supported_power_states')
|
||||
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]
|
||||
|
@ -16,6 +16,8 @@
|
||||
Test class for iRMC Power Driver
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
@ -34,8 +36,6 @@ from ironic.tests.unit.objects import utils as obj_utils
|
||||
INFO_DICT = db_utils.get_test_irmc_info()
|
||||
|
||||
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -47,11 +47,98 @@ class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
|
||||
driver_info=driver_info,
|
||||
instance_uuid=uuidutils.generate_uuid())
|
||||
|
||||
def test__is_expected_power_state(self):
|
||||
target_state = states.SOFT_POWER_OFF
|
||||
boot_status_value = irmc_power.BOOT_STATUS_VALUE['unknown']
|
||||
self.assertTrue(irmc_power._is_expected_power_state(
|
||||
target_state, boot_status_value))
|
||||
|
||||
target_state = states.SOFT_POWER_OFF
|
||||
boot_status_value = irmc_power.BOOT_STATUS_VALUE['off']
|
||||
self.assertTrue(irmc_power._is_expected_power_state(
|
||||
target_state, boot_status_value))
|
||||
|
||||
target_state = states.SOFT_REBOOT
|
||||
boot_status_value = irmc_power.BOOT_STATUS_VALUE['os-running']
|
||||
self.assertTrue(irmc_power._is_expected_power_state(
|
||||
target_state, boot_status_value))
|
||||
|
||||
target_state = states.SOFT_POWER_OFF
|
||||
boot_status_value = irmc_power.BOOT_STATUS_VALUE['os-running']
|
||||
self.assertFalse(irmc_power._is_expected_power_state(
|
||||
target_state, boot_status_value))
|
||||
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch('ironic.drivers.modules.irmc.power.snmp.SNMPClient',
|
||||
spec_set=True, autospec=True)
|
||||
def test__wait_power_state_soft_power_off(self, snmpclient_mock):
|
||||
target_state = states.SOFT_POWER_OFF
|
||||
self.config(snmp_polling_interval=1, group='irmc')
|
||||
self.config(soft_power_off_timeout=3, group='conductor')
|
||||
snmpclient_mock.return_value = mock.Mock(
|
||||
**{'get.side_effect': [8, 8, 2]})
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
irmc_power._wait_power_state(task, target_state)
|
||||
|
||||
task.node.refresh()
|
||||
self.assertIsNone(task.node.last_error)
|
||||
self.assertEqual(states.POWER_OFF, task.node.power_state)
|
||||
self.assertEqual(states.NOSTATE, task.node.target_power_state)
|
||||
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch('ironic.drivers.modules.irmc.power.snmp.SNMPClient',
|
||||
spec_set=True, autospec=True)
|
||||
def test__wait_power_state_soft_reboot(self, snmpclient_mock):
|
||||
target_state = states.SOFT_REBOOT
|
||||
self.config(snmp_polling_interval=1, group='irmc')
|
||||
self.config(soft_power_off_timeout=3, group='conductor')
|
||||
snmpclient_mock.return_value = mock.Mock(
|
||||
**{'get.side_effect': [10, 6, 8]})
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
irmc_power._wait_power_state(task, target_state)
|
||||
|
||||
task.node.refresh()
|
||||
self.assertIsNone(task.node.last_error)
|
||||
self.assertEqual(states.POWER_ON, task.node.power_state)
|
||||
self.assertEqual(states.NOSTATE, task.node.target_power_state)
|
||||
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch('ironic.drivers.modules.irmc.power.snmp.SNMPClient',
|
||||
spec_set=True, autospec=True)
|
||||
def test__wait_power_state_timeout(self, snmpclient_mock):
|
||||
target_state = states.SOFT_POWER_OFF
|
||||
self.config(snmp_polling_interval=1, group='irmc')
|
||||
self.config(soft_power_off_timeout=2, group='conductor')
|
||||
snmpclient_mock.return_value = mock.Mock(
|
||||
**{'get.side_effect': [8, 8, 8]})
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.IRMCOperationError,
|
||||
irmc_power._wait_power_state,
|
||||
task,
|
||||
target_state,
|
||||
timeout=None)
|
||||
|
||||
task.node.refresh()
|
||||
self.assertIsNotNone(task.node.last_error)
|
||||
self.assertEqual(states.ERROR, task.node.power_state)
|
||||
self.assertEqual(states.NOSTATE, task.node.target_power_state)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_power_on_ok(
|
||||
self,
|
||||
attach_boot_iso_if_needed_mock,
|
||||
get_irmc_client_mock):
|
||||
get_irmc_client_mock,
|
||||
_wait_power_state_mock):
|
||||
irmc_client = get_irmc_client_mock.return_value
|
||||
target_state = states.POWER_ON
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
@ -59,21 +146,33 @@ class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
|
||||
irmc_power._set_power_state(task, target_state)
|
||||
attach_boot_iso_if_needed_mock.assert_called_once_with(task)
|
||||
irmc_client.assert_called_once_with(irmc_power.scci.POWER_ON)
|
||||
self.assertFalse(_wait_power_state_mock.called)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
def test__set_power_state_power_off_ok(self,
|
||||
get_irmc_client_mock):
|
||||
get_irmc_client_mock,
|
||||
_wait_power_state_mock):
|
||||
irmc_client = get_irmc_client_mock.return_value
|
||||
target_state = states.POWER_OFF
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
irmc_power._set_power_state(task, target_state)
|
||||
irmc_client.assert_called_once_with(irmc_power.scci.POWER_OFF)
|
||||
self.assertFalse(_wait_power_state_mock.called)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_power_reboot_ok(
|
||||
def test__set_power_state_reboot_ok(
|
||||
self,
|
||||
attach_boot_iso_if_needed_mock,
|
||||
get_irmc_client_mock):
|
||||
get_irmc_client_mock,
|
||||
_wait_power_state_mock):
|
||||
irmc_client = get_irmc_client_mock.return_value
|
||||
target_state = states.REBOOT
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
@ -81,18 +180,72 @@ class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
|
||||
irmc_power._set_power_state(task, target_state)
|
||||
attach_boot_iso_if_needed_mock.assert_called_once_with(task)
|
||||
irmc_client.assert_called_once_with(irmc_power.scci.POWER_RESET)
|
||||
self.assertFalse(_wait_power_state_mock.called)
|
||||
|
||||
def test__set_power_state_invalid_target_state(self,
|
||||
get_irmc_client_mock):
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_soft_reboot_ok(
|
||||
self,
|
||||
attach_boot_iso_if_needed_mock,
|
||||
get_irmc_client_mock,
|
||||
_wait_power_state_mock):
|
||||
irmc_client = get_irmc_client_mock.return_value
|
||||
target_state = states.SOFT_REBOOT
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
irmc_power._set_power_state(task, target_state)
|
||||
attach_boot_iso_if_needed_mock.assert_called_once_with(task)
|
||||
irmc_client.assert_called_once_with(irmc_power.scci.POWER_SOFT_CYCLE)
|
||||
_wait_power_state_mock.assert_called_once_with(task, target_state,
|
||||
timeout=None)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_soft_power_off_ok(self,
|
||||
attach_boot_iso_if_needed_mock,
|
||||
get_irmc_client_mock,
|
||||
_wait_power_state_mock):
|
||||
irmc_client = get_irmc_client_mock.return_value
|
||||
target_state = states.SOFT_POWER_OFF
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
irmc_power._set_power_state(task, target_state)
|
||||
self.assertFalse(attach_boot_iso_if_needed_mock.called)
|
||||
irmc_client.assert_called_once_with(irmc_power.scci.POWER_SOFT_OFF)
|
||||
_wait_power_state_mock.assert_called_once_with(task, target_state,
|
||||
timeout=None)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_invalid_target_state(
|
||||
self,
|
||||
attach_boot_iso_if_needed_mock,
|
||||
_wait_power_state_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_power._set_power_state,
|
||||
task,
|
||||
states.ERROR)
|
||||
self.assertFalse(attach_boot_iso_if_needed_mock.called)
|
||||
self.assertFalse(_wait_power_state_mock.called)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_scci_exception(self,
|
||||
get_irmc_client_mock):
|
||||
attach_boot_iso_if_needed_mock,
|
||||
get_irmc_client_mock,
|
||||
_wait_power_state_mock):
|
||||
irmc_client = get_irmc_client_mock.return_value
|
||||
irmc_client.side_effect = Exception()
|
||||
irmc_power.scci.SCCIClientError = Exception
|
||||
@ -103,6 +256,30 @@ class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
|
||||
irmc_power._set_power_state,
|
||||
task,
|
||||
states.POWER_ON)
|
||||
attach_boot_iso_if_needed_mock.assert_called_once_with(
|
||||
task)
|
||||
self.assertFalse(_wait_power_state_mock.called)
|
||||
|
||||
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
|
||||
def test__set_power_state_snmp_exception(self,
|
||||
attach_boot_iso_if_needed_mock,
|
||||
_wait_power_state_mock):
|
||||
target_state = states.SOFT_REBOOT
|
||||
_wait_power_state_mock.side_effect = exception.SNMPFailure(
|
||||
"fake exception")
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.IRMCOperationError,
|
||||
irmc_power._set_power_state,
|
||||
task,
|
||||
target_state)
|
||||
attach_boot_iso_if_needed_mock.assert_called_once_with(
|
||||
task)
|
||||
_wait_power_state_mock.assert_called_once_with(
|
||||
task, target_state, timeout=None)
|
||||
|
||||
|
||||
class IRMCPowerTestCase(db_base.DbTestCase):
|
||||
@ -158,7 +335,19 @@ class IRMCPowerTestCase(db_base.DbTestCase):
|
||||
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.assert_called_once_with(task, states.POWER_ON)
|
||||
mock_set_power.assert_called_once_with(task, states.POWER_ON,
|
||||
timeout=None)
|
||||
|
||||
@mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
def test_set_power_state_timeout(self, mock_set_power):
|
||||
mock_set_power.return_value = states.POWER_ON
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.set_power_state(task, states.POWER_ON,
|
||||
timeout=2)
|
||||
mock_set_power.assert_called_once_with(task, states.POWER_ON,
|
||||
timeout=2)
|
||||
|
||||
@mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@ -171,7 +360,22 @@ class IRMCPowerTestCase(db_base.DbTestCase):
|
||||
task.driver.power.reboot(task)
|
||||
mock_get_power.assert_called_once_with(
|
||||
task.driver.power, task)
|
||||
mock_set_power.assert_called_once_with(task, states.REBOOT)
|
||||
mock_set_power.assert_called_once_with(task, states.REBOOT,
|
||||
timeout=None)
|
||||
|
||||
@mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
def test_reboot_reboot_timeout(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
|
||||
task.driver.power.reboot(task, timeout=2)
|
||||
mock_get_power.assert_called_once_with(
|
||||
task.driver.power, task)
|
||||
mock_set_power.assert_called_once_with(task, states.REBOOT,
|
||||
timeout=2)
|
||||
|
||||
@mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@ -184,4 +388,19 @@ class IRMCPowerTestCase(db_base.DbTestCase):
|
||||
task.driver.power.reboot(task)
|
||||
mock_get_power.assert_called_once_with(
|
||||
task.driver.power, task)
|
||||
mock_set_power.assert_called_once_with(task, states.POWER_ON)
|
||||
mock_set_power.assert_called_once_with(task, states.POWER_ON,
|
||||
timeout=None)
|
||||
|
||||
@mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True,
|
||||
autospec=True)
|
||||
def test_reboot_power_on_timeout(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_OFF
|
||||
task.driver.power.reboot(task, timeout=2)
|
||||
mock_get_power.assert_called_once_with(
|
||||
task.driver.power, task)
|
||||
mock_set_power.assert_called_once_with(task, states.POWER_ON,
|
||||
timeout=2)
|
||||
|
@ -113,6 +113,8 @@ SCCICLIENT_IRMC_SCCI_SPEC = (
|
||||
'POWER_OFF',
|
||||
'POWER_ON',
|
||||
'POWER_RESET',
|
||||
'POWER_SOFT_CYCLE',
|
||||
'POWER_SOFT_OFF',
|
||||
'MOUNT_CD',
|
||||
'POWER_RAISE_NMI',
|
||||
'UNMOUNT_CD',
|
||||
|
4
releasenotes/notes/irmc-soft-power-281a83647baa4b23.yaml
Normal file
4
releasenotes/notes/irmc-soft-power-281a83647baa4b23.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Adds support for ``soft reboot`` and ``soft power off`` to
|
||||
iRMC driver.
|
Loading…
x
Reference in New Issue
Block a user