Skip lifecycle completion for invalid nodes
When deleting nodes, check node status and skip wait for lifecycle completion if the node is not found or not in active state. Change-Id: I6879b22b85cb4e04c14d946e2c2704997f65ed00 Closes-Bug: 1765836
This commit is contained in:
parent
36ec6ef7bf
commit
cf6cccf070
|
@ -353,11 +353,25 @@ class ClusterAction(base.Action):
|
|||
if not lifecycle_hook:
|
||||
status = base.Action.READY
|
||||
else:
|
||||
status = base.Action.WAITING_LIFECYCLE_COMPLETION
|
||||
# only go into wait lifecycle complete if node exists and
|
||||
# is active
|
||||
node = no.Node.get(self.context, node_id)
|
||||
if not node:
|
||||
LOG.warning('Node %s is not found. '
|
||||
'Skipping wait for lifecycle completion.',
|
||||
node_id)
|
||||
status = base.Action.READY
|
||||
elif node.status != consts.NS_ACTIVE:
|
||||
LOG.warning('Node %s is not in ACTIVE status. '
|
||||
'Skipping wait for lifecycle completion.',
|
||||
node_id)
|
||||
status = base.Action.READY
|
||||
else:
|
||||
status = base.Action.WAITING_LIFECYCLE_COMPLETION
|
||||
|
||||
ao.Action.update(self.context, action_id,
|
||||
{'status': status})
|
||||
if lifecycle_hook:
|
||||
if status == base.Action.WAITING_LIFECYCLE_COMPLETION:
|
||||
# lifecycle_hook_type has to be "zaqar"
|
||||
# post message to zaqar
|
||||
kwargs = {
|
||||
|
|
|
@ -20,6 +20,7 @@ from senlin.engine import dispatcher
|
|||
from senlin.engine.notifications import message as msg
|
||||
from senlin.objects import action as ao
|
||||
from senlin.objects import dependency as dobj
|
||||
from senlin.objects import node as no
|
||||
from senlin.tests.unit.common import base
|
||||
from senlin.tests.unit.common import utils
|
||||
|
||||
|
@ -152,12 +153,14 @@ class ClusterDeleteTest(base.SenlinTestCase):
|
|||
|
||||
@mock.patch.object(ao.Action, 'update')
|
||||
@mock.patch.object(ab.Action, 'create')
|
||||
@mock.patch.object(no.Node, 'get')
|
||||
@mock.patch.object(dobj.Dependency, 'create')
|
||||
@mock.patch.object(msg.Message, 'post_lifecycle_hook_message')
|
||||
@mock.patch.object(dispatcher, 'start_action')
|
||||
@mock.patch.object(ca.ClusterAction, '_wait_for_dependents')
|
||||
def test__delete_nodes_with_lifecycle_hook(self, mock_wait, mock_start,
|
||||
mock_post, mock_dep,
|
||||
mock_node_get,
|
||||
mock_action, mock_update,
|
||||
mock_load):
|
||||
# prepare mocks
|
||||
|
@ -177,6 +180,7 @@ class ClusterDeleteTest(base.SenlinTestCase):
|
|||
}
|
||||
mock_wait.return_value = (action.RES_OK, 'All dependents completed')
|
||||
mock_action.return_value = 'NODE_ACTION_ID'
|
||||
mock_node_get.return_value = mock.Mock(status=consts.NS_ACTIVE)
|
||||
# do it
|
||||
res_code, res_msg = action._delete_nodes(['NODE_ID'])
|
||||
|
||||
|
@ -198,9 +202,80 @@ class ClusterDeleteTest(base.SenlinTestCase):
|
|||
mock_start.assert_called_once_with()
|
||||
mock_wait.assert_called_once_with(action.data['hooks']['timeout'])
|
||||
|
||||
@mock.patch.object(ao.Action, 'update')
|
||||
@mock.patch.object(ab.Action, 'create')
|
||||
@mock.patch.object(no.Node, 'get')
|
||||
@mock.patch.object(dobj.Dependency, 'create')
|
||||
@mock.patch.object(msg.Message, 'post_lifecycle_hook_message')
|
||||
@mock.patch.object(dispatcher, 'start_action')
|
||||
@mock.patch.object(ca.ClusterAction, '_wait_for_dependents')
|
||||
def test__delete_nodes_with_lifecycle_hook_failed_node(
|
||||
self, mock_wait, mock_start, mock_post, mock_dep, mock_node_get,
|
||||
mock_action, mock_update, mock_load):
|
||||
self.delete_nodes_with_lifecycle_hook_invalid_node(
|
||||
mock.Mock(status=consts.NS_ERROR), mock_wait, mock_start,
|
||||
mock_post, mock_dep, mock_node_get, mock_action, mock_update,
|
||||
mock_load)
|
||||
|
||||
@mock.patch.object(ao.Action, 'update')
|
||||
@mock.patch.object(ab.Action, 'create')
|
||||
@mock.patch.object(no.Node, 'get')
|
||||
@mock.patch.object(dobj.Dependency, 'create')
|
||||
@mock.patch.object(msg.Message, 'post_lifecycle_hook_message')
|
||||
@mock.patch.object(dispatcher, 'start_action')
|
||||
@mock.patch.object(ca.ClusterAction, '_wait_for_dependents')
|
||||
def test__delete_nodes_with_lifecycle_hook_missing_node(
|
||||
self, mock_wait, mock_start, mock_post, mock_dep, mock_node_get,
|
||||
mock_action, mock_update, mock_load):
|
||||
self.delete_nodes_with_lifecycle_hook_invalid_node(
|
||||
None, mock_wait, mock_start, mock_post, mock_dep, mock_node_get,
|
||||
mock_action, mock_update, mock_load)
|
||||
|
||||
def delete_nodes_with_lifecycle_hook_invalid_node(
|
||||
self, mock_node_obj, mock_wait, mock_start, mock_post, mock_dep,
|
||||
mock_node_get, mock_action, mock_update, mock_load):
|
||||
# prepare mocks
|
||||
cluster = mock.Mock(id='CLUSTER_ID', desired_capacity=100)
|
||||
mock_load.return_value = cluster
|
||||
# cluster action is real
|
||||
action = ca.ClusterAction(cluster.id, 'CLUSTER_DELETE', self.ctx)
|
||||
action.id = 'CLUSTER_ACTION_ID'
|
||||
action.data = {
|
||||
'hooks': {
|
||||
'timeout': 10,
|
||||
'type': 'zaqar',
|
||||
'params': {
|
||||
'queue': 'myqueue'
|
||||
}
|
||||
}
|
||||
}
|
||||
mock_wait.return_value = (action.RES_OK, 'All dependents completed')
|
||||
mock_action.return_value = 'NODE_ACTION_ID'
|
||||
mock_node_get.return_value = mock_node_obj
|
||||
# do it
|
||||
res_code, res_msg = action._delete_nodes(['NODE_ID'])
|
||||
|
||||
# assertions (other assertions are skipped)
|
||||
self.assertEqual(action.RES_OK, res_code)
|
||||
self.assertEqual('All dependents completed', res_msg)
|
||||
self.assertEqual(1, mock_dep.call_count)
|
||||
mock_action.assert_called_once_with(
|
||||
action.context, 'NODE_ID', 'NODE_DELETE',
|
||||
name='node_delete_NODE_ID', cause='Derived Action with '
|
||||
'Lifecycle Hook')
|
||||
update_calls = [
|
||||
mock.call(action.context, 'NODE_ACTION_ID',
|
||||
{'status': 'READY'}),
|
||||
]
|
||||
mock_update.assert_has_calls(update_calls)
|
||||
mock_post.assert_not_called()
|
||||
mock_start.assert_called_once_with()
|
||||
mock_wait.assert_called_once_with(action.data['hooks']['timeout'])
|
||||
|
||||
@mock.patch.object(ao.Action, 'check_status')
|
||||
@mock.patch.object(ao.Action, 'update')
|
||||
@mock.patch.object(ab.Action, 'create')
|
||||
@mock.patch.object(no.Node, 'get')
|
||||
@mock.patch.object(dobj.Dependency, 'create')
|
||||
@mock.patch.object(msg.Message, 'post_lifecycle_hook_message')
|
||||
@mock.patch.object(dispatcher, 'start_action')
|
||||
|
@ -208,6 +283,7 @@ class ClusterDeleteTest(base.SenlinTestCase):
|
|||
def test__delete_nodes_with_lifecycle_hook_timeout(self, mock_wait,
|
||||
mock_start,
|
||||
mock_post, mock_dep,
|
||||
mock_node_get,
|
||||
mock_action,
|
||||
mock_update,
|
||||
mock_check_status,
|
||||
|
@ -232,6 +308,7 @@ class ClusterDeleteTest(base.SenlinTestCase):
|
|||
(action.RES_OK, 'All dependents completed')
|
||||
]
|
||||
mock_action.return_value = 'NODE_ACTION_ID'
|
||||
mock_node_get.return_value = mock.Mock(status=consts.NS_ACTIVE)
|
||||
mock_check_status.return_value = 'WAITING_LIFECYCLE_COMPLETION'
|
||||
# do it
|
||||
res_code, res_msg = action._delete_nodes(['NODE_ID'])
|
||||
|
|
Loading…
Reference in New Issue