Reduce complexity of node_power_action() function
While attempting to modify the node_power_action() function it caused the complexity to exceed the 'pep8' check limits. This patch reduces the complexity of the node_power_action() function by refactoring it. This will allow future changes to node_power_action() to be allowed. Added unit tests to test the functions that were pulled out. Change-Id: I7f84ff23401997c2b92759c306a7bff0589c7f4c
This commit is contained in:
parent
637abaa499
commit
0d788525df
@ -100,35 +100,32 @@ def node_wait_for_power_state(task, new_state, timeout=None):
|
|||||||
raise exception.PowerStateFailure(pstate=new_state)
|
raise exception.PowerStateFailure(pstate=new_state)
|
||||||
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
def _calculate_target_state(new_state):
|
||||||
def node_power_action(task, new_state, timeout=None):
|
|
||||||
"""Change power state or reset for a node.
|
|
||||||
|
|
||||||
Perform the requested power action if the transition is required.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:param new_state: Any power state from ironic.common.states.
|
|
||||||
:param timeout: timeout (in seconds) positive integer (> 0) for any
|
|
||||||
power state. ``None`` indicates to use default timeout.
|
|
||||||
:raises: InvalidParameterValue when the wrong state is specified
|
|
||||||
or the wrong driver info is specified.
|
|
||||||
:raises: StorageError when a failure occurs updating the node's
|
|
||||||
storage interface upon setting power on.
|
|
||||||
:raises: other exceptions by the node's power driver if something
|
|
||||||
wrong occurred during the power action.
|
|
||||||
|
|
||||||
"""
|
|
||||||
notify_utils.emit_power_set_notification(
|
|
||||||
task, fields.NotificationLevel.INFO, fields.NotificationStatus.START,
|
|
||||||
new_state)
|
|
||||||
node = task.node
|
|
||||||
|
|
||||||
if new_state in (states.POWER_ON, states.REBOOT, states.SOFT_REBOOT):
|
if new_state in (states.POWER_ON, states.REBOOT, states.SOFT_REBOOT):
|
||||||
target_state = states.POWER_ON
|
target_state = states.POWER_ON
|
||||||
elif new_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
|
elif new_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
|
||||||
target_state = states.POWER_OFF
|
target_state = states.POWER_OFF
|
||||||
else:
|
else:
|
||||||
target_state = None
|
target_state = None
|
||||||
|
return target_state
|
||||||
|
|
||||||
|
|
||||||
|
def _can_skip_state_change(task, new_state):
|
||||||
|
"""Check if we can ignore the power state change request for the node.
|
||||||
|
|
||||||
|
Check if we should ignore the requested power state change. This can occur
|
||||||
|
if the requested power state is already the same as our current state. This
|
||||||
|
only works for power on and power off state changes. More complex power
|
||||||
|
state changes, like reboot, are not skipped.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:param new_state: The requested power state to change to. This can be any
|
||||||
|
power state from ironic.common.states.
|
||||||
|
:returns: True if should ignore the requested power state change. False
|
||||||
|
otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
node = task.node
|
||||||
|
|
||||||
def _not_going_to_change():
|
def _not_going_to_change():
|
||||||
# Neither the ironic service nor the hardware has erred. The
|
# Neither the ironic service nor the hardware has erred. The
|
||||||
@ -168,16 +165,45 @@ def node_power_action(task, new_state, timeout=None):
|
|||||||
if curr_state == states.POWER_ON:
|
if curr_state == states.POWER_ON:
|
||||||
if new_state == states.POWER_ON:
|
if new_state == states.POWER_ON:
|
||||||
_not_going_to_change()
|
_not_going_to_change()
|
||||||
return
|
return True
|
||||||
elif curr_state == states.POWER_OFF:
|
elif curr_state == states.POWER_OFF:
|
||||||
if new_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
|
if new_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
|
||||||
_not_going_to_change()
|
_not_going_to_change()
|
||||||
return
|
return True
|
||||||
else:
|
else:
|
||||||
# if curr_state == states.ERROR:
|
# if curr_state == states.ERROR:
|
||||||
# be optimistic and continue action
|
# be optimistic and continue action
|
||||||
LOG.warning("Driver returns ERROR power state for node %s.",
|
LOG.warning("Driver returns ERROR power state for node %s.",
|
||||||
node.uuid)
|
node.uuid)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
def node_power_action(task, new_state, timeout=None):
|
||||||
|
"""Change power state or reset for a node.
|
||||||
|
|
||||||
|
Perform the requested power action if the transition is required.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:param new_state: Any power state from ironic.common.states.
|
||||||
|
:param timeout: timeout (in seconds) positive integer (> 0) for any
|
||||||
|
power state. ``None`` indicates to use default timeout.
|
||||||
|
:raises: InvalidParameterValue when the wrong state is specified
|
||||||
|
or the wrong driver info is specified.
|
||||||
|
:raises: StorageError when a failure occurs updating the node's
|
||||||
|
storage interface upon setting power on.
|
||||||
|
:raises: other exceptions by the node's power driver if something
|
||||||
|
wrong occurred during the power action.
|
||||||
|
|
||||||
|
"""
|
||||||
|
notify_utils.emit_power_set_notification(
|
||||||
|
task, fields.NotificationLevel.INFO, fields.NotificationStatus.START,
|
||||||
|
new_state)
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
if _can_skip_state_change(task, new_state):
|
||||||
|
return
|
||||||
|
target_state = _calculate_target_state(new_state)
|
||||||
|
|
||||||
# Set the target_power_state and clear any last_error, if we're
|
# Set the target_power_state and clear any last_error, if we're
|
||||||
# starting a new operation. This will expose to other processes
|
# starting a new operation. This will expose to other processes
|
||||||
|
@ -558,6 +558,145 @@ class NodePowerActionTestCase(db_base.DbTestCase):
|
|||||||
self.assertIsNone(node['target_power_state'])
|
self.assertIsNone(node['target_power_state'])
|
||||||
self.assertIsNone(node['last_error'])
|
self.assertIsNone(node['last_error'])
|
||||||
|
|
||||||
|
def test__calculate_target_state(self):
|
||||||
|
for new_state in (states.POWER_ON, states.REBOOT, states.SOFT_REBOOT):
|
||||||
|
self.assertEqual(
|
||||||
|
states.POWER_ON,
|
||||||
|
conductor_utils._calculate_target_state(new_state))
|
||||||
|
for new_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
|
||||||
|
self.assertEqual(
|
||||||
|
states.POWER_OFF,
|
||||||
|
conductor_utils._calculate_target_state(new_state))
|
||||||
|
self.assertIsNone(conductor_utils._calculate_target_state('bad_state'))
|
||||||
|
|
||||||
|
def test__can_skip_state_change_different_state(self):
|
||||||
|
"""Test setting node state to different state.
|
||||||
|
|
||||||
|
Test that we should change state if requested state is different from
|
||||||
|
current state.
|
||||||
|
"""
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='fake',
|
||||||
|
last_error='anything but None',
|
||||||
|
power_state=states.POWER_ON)
|
||||||
|
task = task_manager.TaskManager(self.context, node.uuid)
|
||||||
|
|
||||||
|
with mock.patch.object(self.driver.power,
|
||||||
|
'get_power_state') as get_power_mock:
|
||||||
|
get_power_mock.return_value = states.POWER_ON
|
||||||
|
|
||||||
|
result = conductor_utils._can_skip_state_change(
|
||||||
|
task, states.POWER_OFF)
|
||||||
|
|
||||||
|
self.assertFalse(result)
|
||||||
|
get_power_mock.assert_called_once_with(mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch.object(conductor_utils, 'LOG', autospec=True)
|
||||||
|
def test__can_skip_state_change_same_state(self, mock_log):
|
||||||
|
"""Test setting node state to its present state.
|
||||||
|
|
||||||
|
Test that we don't try to set the power state if the requested
|
||||||
|
state is the same as the current state.
|
||||||
|
"""
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='fake',
|
||||||
|
last_error='anything but None',
|
||||||
|
power_state=states.POWER_ON)
|
||||||
|
task = task_manager.TaskManager(self.context, node.uuid)
|
||||||
|
|
||||||
|
with mock.patch.object(self.driver.power,
|
||||||
|
'get_power_state') as get_power_mock:
|
||||||
|
get_power_mock.return_value = states.POWER_ON
|
||||||
|
|
||||||
|
result = conductor_utils._can_skip_state_change(
|
||||||
|
task, states.POWER_ON)
|
||||||
|
|
||||||
|
self.assertTrue(result)
|
||||||
|
node.refresh()
|
||||||
|
get_power_mock.assert_called_once_with(mock.ANY)
|
||||||
|
self.assertEqual(states.POWER_ON, node['power_state'])
|
||||||
|
self.assertEqual(states.NOSTATE, node['target_power_state'])
|
||||||
|
self.assertIsNone(node['last_error'])
|
||||||
|
mock_log.warning.assert_called_once_with(
|
||||||
|
u"Not going to change node %(node)s power state because "
|
||||||
|
u"current state = requested state = '%(state)s'.",
|
||||||
|
{'state': states.POWER_ON, 'node': node.uuid})
|
||||||
|
|
||||||
|
def test__can_skip_state_change_db_not_in_sync(self):
|
||||||
|
"""Test setting node state to its present state if DB is out of sync.
|
||||||
|
|
||||||
|
Under rare conditions (see bug #1403106) database might contain stale
|
||||||
|
information, make sure we fix it.
|
||||||
|
"""
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='fake',
|
||||||
|
last_error='anything but None',
|
||||||
|
power_state=states.POWER_ON)
|
||||||
|
task = task_manager.TaskManager(self.context, node.uuid)
|
||||||
|
|
||||||
|
with mock.patch.object(self.driver.power,
|
||||||
|
'get_power_state') as get_power_mock:
|
||||||
|
get_power_mock.return_value = states.POWER_OFF
|
||||||
|
|
||||||
|
result = conductor_utils._can_skip_state_change(
|
||||||
|
task, states.POWER_OFF)
|
||||||
|
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
node.refresh()
|
||||||
|
get_power_mock.assert_called_once_with(mock.ANY)
|
||||||
|
self.assertEqual(states.POWER_OFF, node['power_state'])
|
||||||
|
self.assertEqual(states.NOSTATE, node['target_power_state'])
|
||||||
|
self.assertIsNone(node['last_error'])
|
||||||
|
|
||||||
|
@mock.patch('ironic.objects.node.NodeSetPowerStateNotification')
|
||||||
|
def test__can_skip_state_change_failed_getting_state_notify(
|
||||||
|
self, mock_notif):
|
||||||
|
"""Test for notification & exception when can't get power state.
|
||||||
|
|
||||||
|
Test to make sure we generate a notification and also that an exception
|
||||||
|
is raised when we can't get the current power state.
|
||||||
|
"""
|
||||||
|
self.config(notification_level='info')
|
||||||
|
self.config(host='my-host')
|
||||||
|
# Required for exception handling
|
||||||
|
mock_notif.__name__ = 'NodeSetPowerStateNotification'
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='fake',
|
||||||
|
power_state=states.POWER_ON)
|
||||||
|
task = task_manager.TaskManager(self.context, node.uuid)
|
||||||
|
|
||||||
|
with mock.patch.object(self.driver.power,
|
||||||
|
'get_power_state') as get_power_state_mock:
|
||||||
|
get_power_state_mock.side_effect = (
|
||||||
|
exception.InvalidParameterValue('failed getting power state'))
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
conductor_utils._can_skip_state_change,
|
||||||
|
task,
|
||||||
|
states.POWER_ON)
|
||||||
|
|
||||||
|
node.refresh()
|
||||||
|
get_power_state_mock.assert_called_once_with(mock.ANY)
|
||||||
|
self.assertEqual(states.POWER_ON, node.power_state)
|
||||||
|
self.assertEqual(states.NOSTATE, node['target_power_state'])
|
||||||
|
self.assertIsNotNone(node.last_error)
|
||||||
|
|
||||||
|
# 1 notification should be sent for the error
|
||||||
|
self.assertEqual(1, mock_notif.call_count)
|
||||||
|
self.assertEqual(1, mock_notif.return_value.emit.call_count)
|
||||||
|
|
||||||
|
notif_args = mock_notif.call_args_list[0][1]
|
||||||
|
|
||||||
|
self.assertNotificationEqual(notif_args,
|
||||||
|
'ironic-conductor', CONF.host,
|
||||||
|
'baremetal.node.power_set.error',
|
||||||
|
obj_fields.NotificationLevel.ERROR)
|
||||||
|
|
||||||
|
|
||||||
class NodeSoftPowerActionTestCase(db_base.DbTestCase):
|
class NodeSoftPowerActionTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user