Make NODE_DELETE operation respect grace_period
This patch fixes the logic of NODE_DELETE operation when it is originated from a RPC request. If a node is a member of a cluster, we should respect the grace_period setting of the deletion policy, when there exists one. This patch also ensures that policy checking is only done when a node action is originated from RPC. For derived actions, we don't do policy checking twice. Change-Id: I287512801e1f2b5d8bccd6f26b5bd24cb3c589d6
This commit is contained in:
parent
55b6b76c29
commit
69480fc3cf
@ -70,6 +70,10 @@ class ClusterAction(base.Action):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.cluster = None
|
self.cluster = None
|
||||||
|
|
||||||
|
def _sleep(self, period):
|
||||||
|
if period:
|
||||||
|
eventlet.sleep(period)
|
||||||
|
|
||||||
def _wait_for_dependents(self):
|
def _wait_for_dependents(self):
|
||||||
"""Wait for dependent actions to complete.
|
"""Wait for dependent actions to complete.
|
||||||
|
|
||||||
@ -300,9 +304,6 @@ class ClusterAction(base.Action):
|
|||||||
|
|
||||||
return self.RES_OK, ''
|
return self.RES_OK, ''
|
||||||
|
|
||||||
def _wait_before_deletion(self, period):
|
|
||||||
eventlet.sleep(period)
|
|
||||||
|
|
||||||
def do_delete(self):
|
def do_delete(self):
|
||||||
"""Handler for the CLUSTER_DELETE action.
|
"""Handler for the CLUSTER_DELETE action.
|
||||||
|
|
||||||
@ -448,8 +449,7 @@ class ClusterAction(base.Action):
|
|||||||
if len(nodes) == 0:
|
if len(nodes) == 0:
|
||||||
return self.RES_OK, reason
|
return self.RES_OK, reason
|
||||||
|
|
||||||
if grace_period:
|
self._sleep(grace_period)
|
||||||
self._wait_before_deletion(grace_period)
|
|
||||||
|
|
||||||
result, new_reason = self._delete_nodes(nodes)
|
result, new_reason = self._delete_nodes(nodes)
|
||||||
if result != self.RES_OK:
|
if result != self.RES_OK:
|
||||||
@ -581,7 +581,7 @@ class ClusterAction(base.Action):
|
|||||||
node_list = self.cluster.nodes
|
node_list = self.cluster.nodes
|
||||||
current_size = len(node_list)
|
current_size = len(node_list)
|
||||||
count, desired, candidates = self._get_action_data(current_size)
|
count, desired, candidates = self._get_action_data(current_size)
|
||||||
grace_period = None
|
grace_period = 0
|
||||||
# if policy is attached to the cluster, use policy data directly,
|
# if policy is attached to the cluster, use policy data directly,
|
||||||
# or parse resize params to get action data.
|
# or parse resize params to get action data.
|
||||||
if count == 0:
|
if count == 0:
|
||||||
@ -593,15 +593,14 @@ class ClusterAction(base.Action):
|
|||||||
return result, reason
|
return result, reason
|
||||||
count, desired, candidates = self._get_action_data(current_size)
|
count, desired, candidates = self._get_action_data(current_size)
|
||||||
elif 'deletion' in self.data:
|
elif 'deletion' in self.data:
|
||||||
grace_period = self.data['deletion'].get('grace_period', None)
|
grace_period = self.data['deletion'].get('grace_period', 0)
|
||||||
if candidates is not None and len(candidates) == 0:
|
if candidates is not None and len(candidates) == 0:
|
||||||
# Choose victims randomly
|
# Choose victims randomly
|
||||||
candidates = scaleutils.nodes_by_random(self.cluster.nodes, count)
|
candidates = scaleutils.nodes_by_random(self.cluster.nodes, count)
|
||||||
|
|
||||||
# delete nodes if necessary
|
# delete nodes if necessary
|
||||||
if desired < current_size:
|
if desired < current_size:
|
||||||
if grace_period is not None:
|
self._sleep(grace_period)
|
||||||
self._wait_before_deletion(grace_period)
|
|
||||||
result, reason = self._delete_nodes(candidates)
|
result, reason = self._delete_nodes(candidates)
|
||||||
# Create new nodes if desired_capacity increased
|
# Create new nodes if desired_capacity increased
|
||||||
else:
|
else:
|
||||||
@ -685,8 +684,8 @@ class ClusterAction(base.Action):
|
|||||||
# We use policy data if any, deletion policy and scaling policy might
|
# We use policy data if any, deletion policy and scaling policy might
|
||||||
# be attached.
|
# be attached.
|
||||||
pd = self.data.get('deletion', None)
|
pd = self.data.get('deletion', None)
|
||||||
grace_period = None
|
grace_period = 0
|
||||||
if pd is not None:
|
if pd:
|
||||||
grace_period = pd.get('grace_period', 0)
|
grace_period = pd.get('grace_period', 0)
|
||||||
candidates = pd.get('candidates', [])
|
candidates = pd.get('candidates', [])
|
||||||
# if scaling policy is attached, get 'count' from action data
|
# if scaling policy is attached, get 'count' from action data
|
||||||
@ -727,8 +726,7 @@ class ClusterAction(base.Action):
|
|||||||
if len(candidates) == 0:
|
if len(candidates) == 0:
|
||||||
candidates = scaleutils.nodes_by_random(self.cluster.nodes, count)
|
candidates = scaleutils.nodes_by_random(self.cluster.nodes, count)
|
||||||
|
|
||||||
if grace_period is not None:
|
self._sleep(grace_period)
|
||||||
self._wait_before_deletion(grace_period)
|
|
||||||
# The policy data may contain destroy flag and grace period option
|
# The policy data may contain destroy flag and grace period option
|
||||||
result, reason = self._delete_nodes(candidates)
|
result, reason = self._delete_nodes(candidates)
|
||||||
|
|
||||||
|
@ -10,14 +10,16 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
|
||||||
from senlin.common.i18n import _
|
from senlin.common.i18n import _
|
||||||
from senlin.common import scaleutils
|
from senlin.common import scaleutils
|
||||||
from senlin.engine.actions import base
|
from senlin.engine.actions import base
|
||||||
from senlin.engine import cluster as cluster_mod
|
from senlin.engine import cluster as cm
|
||||||
from senlin.engine import event as EVENT
|
from senlin.engine import event as EVENT
|
||||||
from senlin.engine import node as node_mod
|
from senlin.engine import node as node_mod
|
||||||
from senlin.engine import senlin_lock
|
from senlin.engine import senlin_lock
|
||||||
from senlin.policies import base as policy_mod
|
from senlin.policies import base as pb
|
||||||
|
|
||||||
|
|
||||||
class NodeAction(base.Action):
|
class NodeAction(base.Action):
|
||||||
@ -58,8 +60,7 @@ class NodeAction(base.Action):
|
|||||||
if self.node.cluster_id and self.cause == base.CAUSE_RPC:
|
if self.node.cluster_id and self.cause == base.CAUSE_RPC:
|
||||||
# If node is created with target cluster specified,
|
# If node is created with target cluster specified,
|
||||||
# check cluster size constraint
|
# check cluster size constraint
|
||||||
cluster = cluster_mod.Cluster.load(self.context,
|
cluster = cm.Cluster.load(self.context, self.node.cluster_id)
|
||||||
self.node.cluster_id)
|
|
||||||
result = scaleutils.check_size_params(
|
result = scaleutils.check_size_params(
|
||||||
cluster, cluster.desired_capacity + 1, None, None, True)
|
cluster, cluster.desired_capacity + 1, None, None, True)
|
||||||
|
|
||||||
@ -96,14 +97,20 @@ class NodeAction(base.Action):
|
|||||||
if self.node.cluster_id and self.cause == base.CAUSE_RPC:
|
if self.node.cluster_id and self.cause == base.CAUSE_RPC:
|
||||||
# If node belongs to a cluster, check size constraint
|
# If node belongs to a cluster, check size constraint
|
||||||
# before deleting it
|
# before deleting it
|
||||||
cluster = cluster_mod.Cluster.load(self.context,
|
cluster = cm.Cluster.load(self.context, self.node.cluster_id)
|
||||||
self.node.cluster_id)
|
|
||||||
result = scaleutils.check_size_params(cluster,
|
result = scaleutils.check_size_params(cluster,
|
||||||
cluster.desired_capacity - 1,
|
cluster.desired_capacity - 1,
|
||||||
None, None, True)
|
None, None, True)
|
||||||
if result:
|
if result:
|
||||||
return self.RES_ERROR, result
|
return self.RES_ERROR, result
|
||||||
|
|
||||||
|
# handle grace_period
|
||||||
|
pd = self.data.get('deletion', None)
|
||||||
|
if pd:
|
||||||
|
grace_period = pd.get('grace_period', 0)
|
||||||
|
if grace_period:
|
||||||
|
eventlet.sleep(grace_period)
|
||||||
|
|
||||||
res = self.node.do_delete(self.context)
|
res = self.node.do_delete(self.context)
|
||||||
if not res:
|
if not res:
|
||||||
return self.RES_ERROR, _('Node deletion failed.')
|
return self.RES_ERROR, _('Node deletion failed.')
|
||||||
@ -139,7 +146,7 @@ class NodeAction(base.Action):
|
|||||||
"""
|
"""
|
||||||
cluster_id = self.inputs.get('cluster_id')
|
cluster_id = self.inputs.get('cluster_id')
|
||||||
# Check the size constraint of parent cluster
|
# Check the size constraint of parent cluster
|
||||||
cluster = cluster_mod.Cluster.load(self.context, cluster_id)
|
cluster = cm.Cluster.load(self.context, cluster_id)
|
||||||
new_capacity = cluster.desired_capacity + 1
|
new_capacity = cluster.desired_capacity + 1
|
||||||
result = scaleutils.check_size_params(cluster, new_capacity,
|
result = scaleutils.check_size_params(cluster, new_capacity,
|
||||||
None, None, True)
|
None, None, True)
|
||||||
@ -159,8 +166,7 @@ class NodeAction(base.Action):
|
|||||||
:returns: A tuple containing the result and the corresponding reason.
|
:returns: A tuple containing the result and the corresponding reason.
|
||||||
"""
|
"""
|
||||||
# Check the size constraint of parent cluster
|
# Check the size constraint of parent cluster
|
||||||
cluster = cluster_mod.Cluster.load(self.context,
|
cluster = cm.Cluster.load(self.context, self.node.cluster_id)
|
||||||
self.node.cluster_id)
|
|
||||||
new_capacity = cluster.desired_capacity - 1
|
new_capacity = cluster.desired_capacity - 1
|
||||||
result = scaleutils.check_size_params(cluster, new_capacity,
|
result = scaleutils.check_size_params(cluster, new_capacity,
|
||||||
None, None, True)
|
None, None, True)
|
||||||
@ -208,22 +214,19 @@ class NodeAction(base.Action):
|
|||||||
# Since node.cluster_id could be reset to '' during action execution,
|
# Since node.cluster_id could be reset to '' during action execution,
|
||||||
# we record it here for policy check and cluster lock release.
|
# we record it here for policy check and cluster lock release.
|
||||||
saved_cluster_id = self.node.cluster_id
|
saved_cluster_id = self.node.cluster_id
|
||||||
if self.node.cluster_id:
|
if (saved_cluster_id and self.cause == base.CAUSE_RPC):
|
||||||
if self.cause == base.CAUSE_RPC:
|
|
||||||
res = senlin_lock.cluster_lock_acquire(
|
res = senlin_lock.cluster_lock_acquire(
|
||||||
self.context,
|
self.context, self.node.cluster_id, self.id, self.owner,
|
||||||
self.node.cluster_id, self.id, self.owner,
|
|
||||||
senlin_lock.NODE_SCOPE, False)
|
senlin_lock.NODE_SCOPE, False)
|
||||||
|
|
||||||
if not res:
|
if not res:
|
||||||
return self.RES_RETRY, _('Failed in locking cluster')
|
return self.RES_RETRY, _('Failed in locking cluster')
|
||||||
|
|
||||||
self.policy_check(self.node.cluster_id, 'BEFORE')
|
self.policy_check(self.node.cluster_id, 'BEFORE')
|
||||||
if self.data['status'] != policy_mod.CHECK_OK:
|
if self.data['status'] != pb.CHECK_OK:
|
||||||
# Don't emit message since policy_check should have done it
|
# Don't emit message since policy_check should have done it
|
||||||
if self.cause == base.CAUSE_RPC:
|
senlin_lock.cluster_lock_release(saved_cluster_id, self.id,
|
||||||
senlin_lock.cluster_lock_release(
|
senlin_lock.NODE_SCOPE)
|
||||||
self.node.cluster_id, self.id, senlin_lock.NODE_SCOPE)
|
|
||||||
|
|
||||||
return self.RES_ERROR, 'Policy check: ' + self.data['reason']
|
return self.RES_ERROR, 'Policy check: ' + self.data['reason']
|
||||||
|
|
||||||
reason = ''
|
reason = ''
|
||||||
@ -235,17 +238,17 @@ class NodeAction(base.Action):
|
|||||||
reason = _('Failed in locking node')
|
reason = _('Failed in locking node')
|
||||||
else:
|
else:
|
||||||
res, reason = self._execute()
|
res, reason = self._execute()
|
||||||
if (self.cause == base.CAUSE_RPC and res == self.RES_OK and
|
if (res == self.RES_OK and saved_cluster_id and
|
||||||
saved_cluster_id):
|
self.cause == base.CAUSE_RPC):
|
||||||
self.policy_check(saved_cluster_id, 'AFTER')
|
self.policy_check(saved_cluster_id, 'AFTER')
|
||||||
if self.data['status'] != policy_mod.CHECK_OK:
|
if self.data['status'] != pb.CHECK_OK:
|
||||||
res = self.RES_ERROR
|
res = self.RES_ERROR
|
||||||
reason = 'Policy check: ' + self.data['reason']
|
reason = 'Policy check: ' + self.data['reason']
|
||||||
else:
|
else:
|
||||||
res = self.RES_OK
|
res = self.RES_OK
|
||||||
finally:
|
finally:
|
||||||
senlin_lock.node_lock_release(self.node.id, self.id)
|
senlin_lock.node_lock_release(self.node.id, self.id)
|
||||||
if self.cause == base.CAUSE_RPC and saved_cluster_id:
|
if saved_cluster_id and self.cause == base.CAUSE_RPC:
|
||||||
senlin_lock.cluster_lock_release(saved_cluster_id, self.id,
|
senlin_lock.cluster_lock_release(saved_cluster_id, self.id,
|
||||||
senlin_lock.NODE_SCOPE)
|
senlin_lock.NODE_SCOPE)
|
||||||
return res, reason
|
return res, reason
|
||||||
|
@ -981,10 +981,10 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
self.assertEqual('Timeout!', res_msg)
|
self.assertEqual('Timeout!', res_msg)
|
||||||
self.assertEqual({}, action.data)
|
self.assertEqual({}, action.data)
|
||||||
|
|
||||||
@mock.patch.object(ca.ClusterAction, '_wait_before_deletion')
|
@mock.patch.object(ca.ClusterAction, '_sleep')
|
||||||
@mock.patch.object(no.Node, 'get')
|
@mock.patch.object(no.Node, 'get')
|
||||||
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
||||||
def test_do_del_nodes(self, mock_delete, mock_get, mock_wait, mock_load):
|
def test_do_del_nodes(self, mock_delete, mock_get, mock_sleep, mock_load):
|
||||||
cluster = mock.Mock()
|
cluster = mock.Mock()
|
||||||
cluster.id = 'FAKE_CLUSTER'
|
cluster.id = 'FAKE_CLUSTER'
|
||||||
cluster.desired_capacity = 4
|
cluster.desired_capacity = 4
|
||||||
@ -1025,21 +1025,47 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
mock_delete.assert_called_once_with(['NODE_1', 'NODE_2'])
|
mock_delete.assert_called_once_with(['NODE_1', 'NODE_2'])
|
||||||
self.assertEqual(2, cluster.desired_capacity)
|
self.assertEqual(2, cluster.desired_capacity)
|
||||||
|
|
||||||
# deletion policy is attached to the action
|
@mock.patch.object(ca.ClusterAction, '_sleep')
|
||||||
|
@mock.patch.object(no.Node, 'get')
|
||||||
|
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
||||||
|
def test_do_del_nodes_with_deletion_policy(self, mock_delete, mock_get,
|
||||||
|
mock_sleep, mock_load):
|
||||||
|
cid = 'FAKE_CLUSTER'
|
||||||
|
cluster = mock.Mock(id=cid, desired_capacity=4)
|
||||||
|
mock_load.return_value = cluster
|
||||||
|
|
||||||
|
action = ca.ClusterAction(cluster.id, 'CLUSTER_ACTION', self.ctx)
|
||||||
|
action.id = 'CLUSTER_ACTION_ID'
|
||||||
|
action.inputs = {'candidates': ['NODE_1', 'NODE_2']}
|
||||||
action.data = {
|
action.data = {
|
||||||
'deletion': {
|
'deletion': {
|
||||||
'count': 2,
|
'count': 2,
|
||||||
'grace_period': 2,
|
'candidates': ['NODE_1', 'NODE_2'],
|
||||||
'destroy_after_deletion': True,
|
'destroy_after_deletion': True,
|
||||||
'candidates': ['NODE_1', 'NODE_2']
|
'grace_period': 2,
|
||||||
|
'reduce_desired_capacity': False,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node1 = mock.Mock(id='NODE_1', cluster_id=cid)
|
||||||
|
node2 = mock.Mock(id='NODE_2', cluster_id=cid)
|
||||||
mock_get.side_effect = [node1, node2]
|
mock_get.side_effect = [node1, node2]
|
||||||
mock_delete.return_value = (action.RES_OK, 'Good to go!')
|
mock_delete.return_value = (action.RES_OK, 'Good to go!')
|
||||||
|
|
||||||
|
# do it
|
||||||
res_code, res_msg = action.do_del_nodes()
|
res_code, res_msg = action.do_del_nodes()
|
||||||
|
|
||||||
|
# assertions
|
||||||
|
self.assertEqual(action.RES_OK, res_code)
|
||||||
|
self.assertEqual('Completed deleting nodes.', res_msg)
|
||||||
|
mock_get.assert_has_calls([
|
||||||
|
mock.call(action.context, 'NODE_1'),
|
||||||
|
mock.call(action.context, 'NODE_2')])
|
||||||
|
mock_delete.assert_called_once_with(['NODE_1', 'NODE_2'])
|
||||||
self.assertTrue(action.data['deletion']['destroy_after_deletion'])
|
self.assertTrue(action.data['deletion']['destroy_after_deletion'])
|
||||||
mock_wait.assert_called_once_with(2)
|
mock_sleep.assert_called_once_with(2)
|
||||||
self.assertEqual(0, cluster.desired_capacity)
|
# Note: desired_capacity not decreased due to policy enforcement
|
||||||
|
self.assertEqual(4, cluster.desired_capacity)
|
||||||
cluster.store.has_calls([
|
cluster.store.has_calls([
|
||||||
mock.call(action.context),
|
mock.call(action.context),
|
||||||
mock.call(action.context)])
|
mock.call(action.context)])
|
||||||
@ -1532,12 +1558,12 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
mock.call(action.context, 'WARNING', 'Things out of control.')])
|
mock.call(action.context, 'WARNING', 'Things out of control.')])
|
||||||
|
|
||||||
@mock.patch.object(scaleutils, 'nodes_by_random')
|
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||||
@mock.patch.object(ca.ClusterAction, '_wait_before_deletion')
|
@mock.patch.object(ca.ClusterAction, '_sleep')
|
||||||
@mock.patch.object(ca.ClusterAction, '_get_action_data')
|
@mock.patch.object(ca.ClusterAction, '_get_action_data')
|
||||||
@mock.patch.object(scaleutils, 'parse_resize_params')
|
@mock.patch.object(scaleutils, 'parse_resize_params')
|
||||||
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
||||||
def test_do_resize_shrink(self, mock_delete, mock_parse, mock_get,
|
def test_do_resize_shrink(self, mock_delete, mock_parse, mock_get,
|
||||||
mock_wait, mock_select, mock_load):
|
mock_sleep, mock_select, mock_load):
|
||||||
cluster = mock.Mock()
|
cluster = mock.Mock()
|
||||||
cluster.id = 'CID'
|
cluster.id = 'CID'
|
||||||
cluster.desired_capacity = 10
|
cluster.desired_capacity = 10
|
||||||
@ -1583,14 +1609,30 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
mock.call(action.context, 'RESIZING', 'Cluster resize started.'),
|
mock.call(action.context, 'RESIZING', 'Cluster resize started.'),
|
||||||
mock.call(action.context, 'ACTIVE', 'Cluster resize succeeded.',
|
mock.call(action.context, 'ACTIVE', 'Cluster resize succeeded.',
|
||||||
desired_capacity=8)])
|
desired_capacity=8)])
|
||||||
|
mock_sleep.assert_called_once_with(0)
|
||||||
|
|
||||||
# deletion policy is attached to the action
|
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||||
cluster.nodes = []
|
@mock.patch.object(ca.ClusterAction, '_sleep')
|
||||||
|
@mock.patch.object(ca.ClusterAction, '_get_action_data')
|
||||||
|
@mock.patch.object(scaleutils, 'parse_resize_params')
|
||||||
|
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
||||||
|
def test_do_resize_shrink_with_deletion_policy(self, mock_delete,
|
||||||
|
mock_parse, mock_get,
|
||||||
|
mock_sleep, mock_select,
|
||||||
|
mock_load):
|
||||||
|
|
||||||
|
cluster = mock.Mock(id='CID', desired_capacity=10, nodes=[],
|
||||||
|
ACTIVE='ACTIVE', RESIZING='RESIZING')
|
||||||
for n in range(10):
|
for n in range(10):
|
||||||
node = mock.Mock()
|
node = mock.Mock()
|
||||||
node.id = 'NODE-ID-%s' % (n + 1)
|
node.id = 'NODE-ID-%s' % (n + 1)
|
||||||
cluster.nodes.append(node)
|
cluster.nodes.append(node)
|
||||||
|
|
||||||
|
mock_parse.return_value = 'OK', ''
|
||||||
|
mock_load.return_value = cluster
|
||||||
mock_get.return_value = (2, 9, [cluster.nodes[0]])
|
mock_get.return_value = (2, 9, [cluster.nodes[0]])
|
||||||
|
|
||||||
|
action = ca.ClusterAction(cluster.id, 'CLUSTER_ACTION', self.ctx)
|
||||||
action.data = {
|
action.data = {
|
||||||
'deletion': {
|
'deletion': {
|
||||||
'count': 1,
|
'count': 1,
|
||||||
@ -1598,11 +1640,16 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
'destroy_after_deletion': True
|
'destroy_after_deletion': True
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mock_delete.return_value = (action.RES_OK, 'All dependents completed.')
|
||||||
|
|
||||||
|
# deletion policy is attached to the action
|
||||||
res_code, res_msg = action.do_resize()
|
res_code, res_msg = action.do_resize()
|
||||||
|
|
||||||
self.assertEqual({'deletion': {'count': 1, 'grace_period': 2,
|
self.assertEqual({'deletion': {'count': 1, 'grace_period': 2,
|
||||||
'destroy_after_deletion': True}},
|
'destroy_after_deletion': True}},
|
||||||
action.data)
|
action.data)
|
||||||
mock_wait.assert_called_once_with(2)
|
mock_sleep.assert_called_once_with(2)
|
||||||
|
|
||||||
def test_do_resize_failed_checking(self, mock_load):
|
def test_do_resize_failed_checking(self, mock_load):
|
||||||
cluster = mock.Mock()
|
cluster = mock.Mock()
|
||||||
@ -1907,9 +1954,9 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
mock.call(action.context, cluster.ACTIVE,
|
mock.call(action.context, cluster.ACTIVE,
|
||||||
'Cluster scaling succeeded.', desired_capacity=9)])
|
'Cluster scaling succeeded.', desired_capacity=9)])
|
||||||
|
|
||||||
@mock.patch.object(ca.ClusterAction, '_wait_before_deletion')
|
@mock.patch.object(ca.ClusterAction, '_sleep')
|
||||||
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
||||||
def test_do_scale_in_with_pd_no_input(self, mock_delete, mock_wait,
|
def test_do_scale_in_with_pd_no_input(self, mock_delete, mock_sleep,
|
||||||
mock_load):
|
mock_load):
|
||||||
cluster = mock.Mock()
|
cluster = mock.Mock()
|
||||||
cluster.id = 'CID'
|
cluster.id = 'CID'
|
||||||
@ -1949,7 +1996,7 @@ class ClusterActionTest(base.SenlinTestCase):
|
|||||||
'Cluster scale in started.'),
|
'Cluster scale in started.'),
|
||||||
mock.call(action.context, cluster.ACTIVE,
|
mock.call(action.context, cluster.ACTIVE,
|
||||||
'Cluster scaling succeeded.', desired_capacity=3)])
|
'Cluster scaling succeeded.', desired_capacity=3)])
|
||||||
mock_wait.assert_called_once_with(2)
|
mock_sleep.assert_called_once_with(2)
|
||||||
|
|
||||||
@mock.patch.object(scaleutils, 'nodes_by_random')
|
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||||
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
@mock.patch.object(ca.ClusterAction, '_delete_nodes')
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import eventlet
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from senlin.common import scaleutils
|
from senlin.common import scaleutils
|
||||||
@ -182,21 +183,18 @@ class NodeActionTest(base.SenlinTestCase):
|
|||||||
self.assertEqual(1, cluster.desired_capacity)
|
self.assertEqual(1, cluster.desired_capacity)
|
||||||
cluster.remove_node.assert_called_once_with(node.id)
|
cluster.remove_node.assert_called_once_with(node.id)
|
||||||
|
|
||||||
|
@mock.patch.object(eventlet, 'sleep')
|
||||||
@mock.patch.object(scaleutils, 'check_size_params')
|
@mock.patch.object(scaleutils, 'check_size_params')
|
||||||
@mock.patch.object(cluster_mod.Cluster, 'load')
|
@mock.patch.object(cluster_mod.Cluster, 'load')
|
||||||
def test_do_delete_with_forced_changing_capacity(self, mock_c_load,
|
def test_do_delete_with_forced_changing_capacity(
|
||||||
mock_check, mock_load):
|
self, mock_c_load, mock_check, mock_sleep, mock_load):
|
||||||
cluster = mock.Mock()
|
cluster = mock.Mock(id='CID', desired_capacity=1)
|
||||||
cluster.id = 'CID'
|
|
||||||
cluster.desired_capacity = 1
|
|
||||||
mock_c_load.return_value = cluster
|
mock_c_load.return_value = cluster
|
||||||
node = mock.Mock()
|
node = mock.Mock(id='NID', cluster_id='CID')
|
||||||
node.id = 'NID'
|
|
||||||
node.do_delete = mock.Mock(return_value=None)
|
node.do_delete = mock.Mock(return_value=None)
|
||||||
node.cluster_id = cluster.id
|
|
||||||
mock_load.return_value = node
|
mock_load.return_value = node
|
||||||
mock_check.return_value = None
|
mock_check.return_value = None
|
||||||
action = node_action.NodeAction(node.id, 'ACTION', self.ctx,
|
action = node_action.NodeAction('NID', 'ACTION', self.ctx,
|
||||||
cause=base_action.CAUSE_RPC)
|
cause=base_action.CAUSE_RPC)
|
||||||
action.data = {
|
action.data = {
|
||||||
'deletion': {
|
'deletion': {
|
||||||
@ -214,19 +212,49 @@ class NodeActionTest(base.SenlinTestCase):
|
|||||||
cluster.store.assert_called_once_with(action.context)
|
cluster.store.assert_called_once_with(action.context)
|
||||||
self.assertEqual(0, cluster.desired_capacity)
|
self.assertEqual(0, cluster.desired_capacity)
|
||||||
cluster.remove_node.assert_called_once_with(node.id)
|
cluster.remove_node.assert_called_once_with(node.id)
|
||||||
|
self.assertEqual(0, mock_sleep.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(eventlet, 'sleep')
|
||||||
@mock.patch.object(scaleutils, 'check_size_params')
|
@mock.patch.object(scaleutils, 'check_size_params')
|
||||||
@mock.patch.object(cluster_mod.Cluster, 'load')
|
@mock.patch.object(cluster_mod.Cluster, 'load')
|
||||||
def test_do_delete_for_derived_action(self, mock_c_load,
|
def test_do_delete_with_grace_period(
|
||||||
mock_check, mock_load):
|
self, mock_c_load, mock_check, mock_sleep, mock_load):
|
||||||
node = mock.Mock()
|
cluster = mock.Mock(id='CID', desired_capacity=1)
|
||||||
node.id = 'NID'
|
mock_c_load.return_value = cluster
|
||||||
|
node = mock.Mock(id='NID', cluster_id='CID')
|
||||||
|
node.do_delete = mock.Mock(return_value=None)
|
||||||
|
mock_load.return_value = node
|
||||||
|
mock_check.return_value = None
|
||||||
|
action = node_action.NodeAction('NID', 'ACTION', self.ctx,
|
||||||
|
cause=base_action.CAUSE_RPC)
|
||||||
|
action.data = {
|
||||||
|
'deletion': {
|
||||||
|
'grace_period': 123,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.do_delete = mock.Mock(return_value=mock.Mock())
|
||||||
|
|
||||||
|
res_code, res_msg = action.do_delete()
|
||||||
|
|
||||||
|
self.assertEqual(action.RES_OK, res_code)
|
||||||
|
mock_check.assert_called_once_with(cluster, 0, None, None, True)
|
||||||
|
mock_c_load.assert_called_once_with(action.context, 'CID')
|
||||||
|
cluster.store.assert_called_once_with(action.context)
|
||||||
|
self.assertEqual(0, cluster.desired_capacity)
|
||||||
|
cluster.remove_node.assert_called_once_with(node.id)
|
||||||
|
mock_sleep.assert_called_once_with(123)
|
||||||
|
|
||||||
|
@mock.patch.object(eventlet, 'sleep')
|
||||||
|
@mock.patch.object(scaleutils, 'check_size_params')
|
||||||
|
@mock.patch.object(cluster_mod.Cluster, 'load')
|
||||||
|
def test_do_delete_for_derived_action(self, mock_c_load, mock_check,
|
||||||
|
mock_sleep, mock_load):
|
||||||
|
node = mock.Mock(id='NID', cluster_id='CLUSTER_ID')
|
||||||
node.do_delete = mock.Mock(return_value=None)
|
node.do_delete = mock.Mock(return_value=None)
|
||||||
node.cluster_id = 'CLUSTER_ID'
|
|
||||||
mock_load.return_value = node
|
mock_load.return_value = node
|
||||||
action = node_action.NodeAction(node.id, 'ACTION', self.ctx,
|
action = node_action.NodeAction(node.id, 'ACTION', self.ctx,
|
||||||
cause=base_action.CAUSE_DERIVED)
|
cause=base_action.CAUSE_DERIVED)
|
||||||
|
|
||||||
node.do_delete = mock.Mock(return_value=mock.Mock())
|
node.do_delete = mock.Mock(return_value=mock.Mock())
|
||||||
|
|
||||||
res_code, res_msg = action.do_delete()
|
res_code, res_msg = action.do_delete()
|
||||||
@ -235,6 +263,7 @@ class NodeActionTest(base.SenlinTestCase):
|
|||||||
self.assertEqual(0, mock_check.call_count)
|
self.assertEqual(0, mock_check.call_count)
|
||||||
self.assertEqual(0, mock_c_load.call_count)
|
self.assertEqual(0, mock_c_load.call_count)
|
||||||
mock_load.assert_called_once_with(action.context, node_id='NID')
|
mock_load.assert_called_once_with(action.context, node_id='NID')
|
||||||
|
self.assertEqual(0, mock_sleep.call_count)
|
||||||
|
|
||||||
def test_do_update(self, mock_load):
|
def test_do_update(self, mock_load):
|
||||||
node = mock.Mock()
|
node = mock.Mock()
|
||||||
@ -507,6 +536,40 @@ class NodeActionTest(base.SenlinTestCase):
|
|||||||
lock.NODE_SCOPE)
|
lock.NODE_SCOPE)
|
||||||
mock_check.assert_called_once_with('FAKE_CLUSTER', 'BEFORE')
|
mock_check.assert_called_once_with('FAKE_CLUSTER', 'BEFORE')
|
||||||
|
|
||||||
|
@mock.patch.object(lock, 'cluster_lock_acquire')
|
||||||
|
@mock.patch.object(lock, 'cluster_lock_release')
|
||||||
|
@mock.patch.object(lock, 'node_lock_acquire')
|
||||||
|
@mock.patch.object(lock, 'node_lock_release')
|
||||||
|
@mock.patch.object(base_action.Action, 'policy_check')
|
||||||
|
def test_execute_no_policy_check(self, mock_check,
|
||||||
|
mock_nl_release, mock_nl_acquire,
|
||||||
|
mock_cl_release, mock_cl_acquire,
|
||||||
|
mock_load):
|
||||||
|
node_id = 'NODE_ID'
|
||||||
|
node = mock.Mock(id=node_id, cluster_id='FAKE_CLUSTER')
|
||||||
|
mock_load.return_value = node
|
||||||
|
action = node_action.NodeAction(node_id, 'NODE_FLY', self.ctx,
|
||||||
|
cause=base_action.CAUSE_DERIVED)
|
||||||
|
action.id = 'ACTION_ID'
|
||||||
|
action.owner = 'OWNER'
|
||||||
|
mock_exec = self.patchobject(action, '_execute',
|
||||||
|
return_value=(action.RES_OK, 'Good'))
|
||||||
|
mock_nl_acquire.return_value = action.id
|
||||||
|
|
||||||
|
res_code, res_msg = action.execute()
|
||||||
|
|
||||||
|
self.assertEqual(action.RES_OK, res_code)
|
||||||
|
self.assertEqual('Good', res_msg)
|
||||||
|
mock_load.assert_called_once_with(action.context, node_id=node_id)
|
||||||
|
self.assertEqual(0, mock_cl_acquire.call_count)
|
||||||
|
self.assertEqual(0, mock_cl_release.call_count)
|
||||||
|
mock_nl_acquire.assert_called_once_with(self.ctx, node_id,
|
||||||
|
action.id, action.owner,
|
||||||
|
False)
|
||||||
|
mock_nl_release.assert_called_once_with(node_id, action.id)
|
||||||
|
mock_exec.assert_called_once_with()
|
||||||
|
self.assertEqual(0, mock_check.call_count)
|
||||||
|
|
||||||
@mock.patch.object(lock, 'cluster_lock_acquire')
|
@mock.patch.object(lock, 'cluster_lock_acquire')
|
||||||
@mock.patch.object(lock, 'cluster_lock_release')
|
@mock.patch.object(lock, 'cluster_lock_release')
|
||||||
@mock.patch.object(base_action.Action, 'policy_check')
|
@mock.patch.object(base_action.Action, 'policy_check')
|
||||||
|
Loading…
Reference in New Issue
Block a user