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:
Duc Truong 2018-04-20 19:02:09 +00:00
parent 36ec6ef7bf
commit cf6cccf070
2 changed files with 93 additions and 2 deletions

View File

@ -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 = {

View File

@ -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'])