Further rework lb_policy
This patch further reworks lb_policy to make it support cases where no scaling_policy and/or deletion_policy is attached to cluster. Change-Id: I529f74f59dec9e4f1ed590b230bce4b075749dd4 Closes-Bug: #1535508 CLoses-Bug: #1536481
This commit is contained in:
parent
c4675804f7
commit
92887f5312
|
@ -161,7 +161,11 @@ class ClusterAction(base.Action):
|
|||
# Wait for cluster creation to complete
|
||||
res, reason = self._wait_for_dependents()
|
||||
if res == self.RES_OK:
|
||||
self.outputs['nodes_added'] = [n.id for n in nodes]
|
||||
nodes_added = [n.id for n in nodes]
|
||||
self.outputs['nodes_added'] = nodes_added
|
||||
creation = self.data.get('creation', {})
|
||||
creation['nodes'] = nodes_added
|
||||
self.data['creation'] = creation
|
||||
for node in nodes:
|
||||
self.cluster.add_node(node)
|
||||
|
||||
|
@ -395,7 +399,11 @@ class ClusterAction(base.Action):
|
|||
if result != self.RES_OK:
|
||||
reason = new_reason
|
||||
else:
|
||||
self.outputs['nodes_added'] = [node.id for node in nodes]
|
||||
nodes_added = [n.id for n in nodes]
|
||||
self.outputs['nodes_added'] = nodes_added
|
||||
creation = self.data.get('creation', {})
|
||||
creation['nodes'] = nodes_added
|
||||
self.data['creation'] = creation
|
||||
for node in nodes:
|
||||
self.cluster.add_node(node)
|
||||
|
||||
|
@ -585,7 +593,7 @@ class ClusterAction(base.Action):
|
|||
return result, reason
|
||||
count, desired, candidates = self._get_action_data(current_size)
|
||||
elif 'deletion' in self.data:
|
||||
grace_period = self.data['deletion']['grace_period']
|
||||
grace_period = self.data['deletion'].get('grace_period', None)
|
||||
if candidates is not None and len(candidates) == 0:
|
||||
# Choose victims randomly
|
||||
candidates = scaleutils.nodes_by_random(self.cluster.nodes, count)
|
||||
|
|
|
@ -17,6 +17,7 @@ from senlin.common import constraints
|
|||
from senlin.common import consts
|
||||
from senlin.common.i18n import _
|
||||
from senlin.common.i18n import _LW
|
||||
from senlin.common import scaleutils
|
||||
from senlin.common import schema
|
||||
from senlin.db import api as db_api
|
||||
from senlin.drivers import base as driver_base
|
||||
|
@ -271,6 +272,40 @@ class LoadBalancingPolicy(base.Policy):
|
|||
|
||||
return True, reason
|
||||
|
||||
def _get_delete_candidates(self, cluster_id, action):
|
||||
deletion = action.data.get('deletion', None)
|
||||
# No deletion field in action.data which means no scaling
|
||||
# policy or deletion policy is attached.
|
||||
if deletion is None:
|
||||
candidates = None
|
||||
if action.action == consts.CLUSTER_DEL_NODES:
|
||||
# Get candidates from action.input
|
||||
candidates = action.inputs.get('candidates', [])
|
||||
count = len(candidates)
|
||||
elif action.action == consts.CLUSTER_RESIZE:
|
||||
# Calculate deletion count based on action input
|
||||
db_cluster = db_api.cluster_get(action.context, cluster_id)
|
||||
scaleutils.parse_resize_params(action, db_cluster)
|
||||
if 'deletion' not in action.data:
|
||||
return []
|
||||
else:
|
||||
count = action.data['deletion']['count']
|
||||
else: # action.action == consts.CLUSTER_SCALE_IN
|
||||
count = 1
|
||||
else:
|
||||
count = deletion.get('count', 0)
|
||||
candidates = deletion.get('candidates', None)
|
||||
|
||||
# Still no candidates available, pick count of nodes randomly
|
||||
if candidates is None:
|
||||
nodes = db_api.node_get_all_by_cluster(action.context,
|
||||
cluster_id=cluster_id)
|
||||
if count > len(nodes):
|
||||
count = len(nodes)
|
||||
candidates = scaleutils.nodes_by_random(nodes, count)
|
||||
|
||||
return candidates
|
||||
|
||||
def pre_op(self, cluster_id, action):
|
||||
"""Routine to be called before an action has been executed.
|
||||
|
||||
|
@ -282,9 +317,9 @@ class LoadBalancingPolicy(base.Policy):
|
|||
:param action: The action object that triggered this operation.
|
||||
:returns: Nothing.
|
||||
"""
|
||||
# TODO(YanyanHu): get deleted nodes list from action.data
|
||||
nodes_removed = action.outputs.get('nodes_removed', [])
|
||||
if len(nodes_removed) == 0:
|
||||
|
||||
candidates = self._get_delete_candidates(cluster_id, action)
|
||||
if len(candidates) == 0:
|
||||
return
|
||||
|
||||
db_cluster = db_api.cluster_get(action.context, cluster_id)
|
||||
|
@ -297,7 +332,7 @@ class LoadBalancingPolicy(base.Policy):
|
|||
pool_id = policy_data['pool']
|
||||
|
||||
# Remove nodes that will be deleted from lb pool
|
||||
for node_id in nodes_removed:
|
||||
for node_id in candidates:
|
||||
node = node_mod.Node.load(action.context, node_id=node_id)
|
||||
member_id = node.data.get('lb_member', None)
|
||||
if member_id is None:
|
||||
|
@ -312,6 +347,10 @@ class LoadBalancingPolicy(base.Policy):
|
|||
'node(s) from lb pool.')
|
||||
return
|
||||
|
||||
deletion = action.data.get('deletion', {})
|
||||
deletion.update({'count': len(candidates), 'candidates': candidates})
|
||||
action.data.update({'deletion': deletion})
|
||||
|
||||
return
|
||||
|
||||
def post_op(self, cluster_id, action):
|
||||
|
@ -325,8 +364,11 @@ class LoadBalancingPolicy(base.Policy):
|
|||
:param action: The action object that triggered this operation.
|
||||
:returns: Nothing.
|
||||
"""
|
||||
# TODO(YanyanHu): get added nodes list from action.data
|
||||
nodes_added = action.outputs.get('nodes_added', [])
|
||||
|
||||
# TODO(Yanyanhu): Need special handling for cross-az scenario
|
||||
# which is supported by Neutron lbaas.
|
||||
creation = action.data.get('creation', None)
|
||||
nodes_added = creation.get('nodes', []) if creation else []
|
||||
if len(nodes_added) == 0:
|
||||
return
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import mock
|
|||
from oslo_context import context as oslo_context
|
||||
|
||||
from senlin.common import consts
|
||||
from senlin.common import scaleutils
|
||||
from senlin.db import api as db_api
|
||||
from senlin.drivers import base as driver_base
|
||||
from senlin.engine import cluster_policy
|
||||
|
@ -203,6 +204,113 @@ class TestLoadBalancingPolicy(base.SenlinTestCase):
|
|||
self.assertEqual((False, 'Failed in adding node into lb pool'), res)
|
||||
self.lb_driver.lb_delete.assert_called_once_with(**lb_data)
|
||||
|
||||
def test_get_delete_candidates_no_deletion_data_del_nodes(self):
|
||||
action = mock.Mock()
|
||||
action.data = {}
|
||||
action.action = consts.CLUSTER_DEL_NODES
|
||||
action.inputs = {'candidates': ['node1', 'node2']}
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
res = policy._get_delete_candidates('CLUSTERID', action)
|
||||
self.assertEqual(['node1', 'node2'], res)
|
||||
|
||||
@mock.patch.object(db_api, 'node_get_all_by_cluster')
|
||||
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||
def test_get_delete_candidates_no_deletion_data_scale_in(self,
|
||||
m_nodes_random,
|
||||
m_node_get):
|
||||
|
||||
action = mock.Mock()
|
||||
self.context = utils.dummy_context()
|
||||
action.data = {}
|
||||
action.action = consts.CLUSTER_SCALE_IN
|
||||
m_node_get.return_value = ['node1', 'node2', 'node3']
|
||||
m_nodes_random.return_value = ['node1', 'node3']
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
res = policy._get_delete_candidates('CLUSTERID', action)
|
||||
m_node_get.assert_called_once_with(action.context,
|
||||
cluster_id='CLUSTERID')
|
||||
m_nodes_random.assert_called_once_with(['node1', 'node2', 'node3'], 1)
|
||||
|
||||
self.assertEqual(['node1', 'node3'], res)
|
||||
|
||||
@mock.patch.object(db_api, 'node_get_all_by_cluster')
|
||||
@mock.patch.object(db_api, 'cluster_get')
|
||||
@mock.patch.object(scaleutils, 'parse_resize_params')
|
||||
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||
def test_get_delete_candidates_no_deletion_data_resize(self,
|
||||
m_nodes_random,
|
||||
m_parse_param,
|
||||
m_cluster_get,
|
||||
m_node_get):
|
||||
def _parse_param(action, cluster):
|
||||
action.data = {'deletion': {'count': 2}}
|
||||
|
||||
action = mock.Mock()
|
||||
self.context = utils.dummy_context()
|
||||
action.data = {}
|
||||
action.action = consts.CLUSTER_RESIZE
|
||||
m_parse_param.side_effect = _parse_param
|
||||
m_node_get.return_value = ['node1', 'node2', 'node3']
|
||||
m_cluster_get.return_value = 'cluster1'
|
||||
m_nodes_random.return_value = ['node1', 'node3']
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
res = policy._get_delete_candidates('CLUSTERID', action)
|
||||
m_cluster_get.assert_called_once_with(action.context,
|
||||
'CLUSTERID')
|
||||
m_parse_param.assert_called_once_with(action, 'cluster1')
|
||||
m_node_get.assert_called_once_with(action.context,
|
||||
cluster_id='CLUSTERID')
|
||||
m_nodes_random.assert_called_once_with(['node1', 'node2', 'node3'], 2)
|
||||
|
||||
self.assertEqual(['node1', 'node3'], res)
|
||||
|
||||
@mock.patch.object(db_api, 'node_get_all_by_cluster')
|
||||
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||
def test_get_delete_candidates_deletion_no_candidates(self,
|
||||
m_nodes_random,
|
||||
m_node_get):
|
||||
action = mock.Mock()
|
||||
self.context = utils.dummy_context()
|
||||
action.data = {'deletion': {'count': 1}}
|
||||
m_node_get.return_value = ['node1', 'node2', 'node3']
|
||||
m_nodes_random.return_value = ['node2']
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
res = policy._get_delete_candidates('CLUSTERID', action)
|
||||
m_node_get.assert_called_once_with(action.context,
|
||||
cluster_id='CLUSTERID')
|
||||
m_nodes_random.assert_called_once_with(['node1', 'node2', 'node3'], 1)
|
||||
|
||||
self.assertEqual(['node2'], res)
|
||||
|
||||
@mock.patch.object(db_api, 'node_get_all_by_cluster')
|
||||
@mock.patch.object(scaleutils, 'nodes_by_random')
|
||||
def test_get_delete_candidates_deletion_count_over_size(self,
|
||||
m_nodes_random,
|
||||
m_node_get):
|
||||
action = mock.Mock()
|
||||
self.context = utils.dummy_context()
|
||||
action.data = {'deletion': {'count': 4}}
|
||||
m_node_get.return_value = ['node1', 'node2', 'node3']
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
policy._get_delete_candidates('CLUSTERID', action)
|
||||
m_node_get.assert_called_once_with(action.context,
|
||||
cluster_id='CLUSTERID')
|
||||
m_nodes_random.assert_called_once_with(['node1', 'node2', 'node3'], 3)
|
||||
|
||||
def test_get_delete_candidates_deletion_with_candidates(self):
|
||||
action = mock.Mock()
|
||||
self.context = utils.dummy_context()
|
||||
action.data = {'deletion': {'count': 1, 'candidates': ['node3']}}
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
res = policy._get_delete_candidates('CLUSTERID', action)
|
||||
self.assertEqual(['node3'], res)
|
||||
|
||||
|
||||
@mock.patch.object(lb_policy.LoadBalancingPolicy, '_build_conn_params')
|
||||
@mock.patch.object(cluster_policy.ClusterPolicy, 'load')
|
||||
|
@ -294,7 +402,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
|
||||
def test_post_op_no_nodes(self, m_extract, m_load, m_conn):
|
||||
action = mock.Mock()
|
||||
action.outputs = {}
|
||||
action.data = {}
|
||||
|
||||
policy = lb_policy.LoadBalancingPolicy('test-policy', self.spec)
|
||||
|
||||
|
@ -313,7 +421,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
node1.data = {}
|
||||
node2.data = {}
|
||||
action = mock.Mock()
|
||||
action.outputs = {'nodes_added': ['NODE1_ID', 'NODE2_ID']}
|
||||
action.data = {'creation': {'nodes': ['NODE1_ID', 'NODE2_ID']}}
|
||||
action.context = 'action_context'
|
||||
action.action = consts.CLUSTER_RESIZE
|
||||
cp = mock.Mock()
|
||||
|
@ -367,7 +475,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
node1.data = {'lb_member': 'MEMBER1_ID'}
|
||||
node2.data = {}
|
||||
action = mock.Mock()
|
||||
action.outputs = {'nodes_added': ['NODE1_ID', 'NODE2_ID']}
|
||||
action.data = {'creation': {'nodes': ['NODE1_ID', 'NODE2_ID']}}
|
||||
action.context = 'action_context'
|
||||
action.action = consts.CLUSTER_RESIZE
|
||||
policy_data = {
|
||||
|
@ -395,7 +503,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
node1.data = {}
|
||||
action = mock.Mock()
|
||||
action.data = {}
|
||||
action.outputs = {'nodes_added': ['NODE1_ID']}
|
||||
action.data = {'creation': {'nodes': ['NODE1_ID']}}
|
||||
action.context = 'action_context'
|
||||
action.action = consts.CLUSTER_RESIZE
|
||||
self.lb_driver.member_add.return_value = None
|
||||
|
@ -430,7 +538,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
node2.data = {'lb_member': 'MEMBER2_ID'}
|
||||
action = mock.Mock()
|
||||
action.data = {}
|
||||
action.outputs = {'nodes_removed': ['NODE1_ID', 'NODE2_ID']}
|
||||
action.data = {'deletion': {'candidates': ['NODE1_ID', 'NODE2_ID']}}
|
||||
action.context = 'action_context'
|
||||
action.action = consts.CLUSTER_DEL_NODES
|
||||
cp = mock.Mock()
|
||||
|
@ -472,6 +580,10 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
]
|
||||
self.lb_driver.member_remove.assert_has_calls(calls_member_del)
|
||||
|
||||
expected_data = {'deletion': {'candidates': ['NODE1_ID', 'NODE2_ID'],
|
||||
'count': 2}}
|
||||
self.assertEqual(expected_data, action.data)
|
||||
|
||||
@mock.patch.object(node_mod.Node, 'load')
|
||||
@mock.patch.object(db_api, 'cluster_get')
|
||||
def test_pre_op_del_nodes_not_in_pool(self, m_cluster_get, m_node_load,
|
||||
|
@ -482,7 +594,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
node1.data = {}
|
||||
node2.data = {'lb_member': 'MEMBER2_ID'}
|
||||
action = mock.Mock()
|
||||
action.outputs = {'nodes_removed': ['NODE1_ID', 'NODE2_ID']}
|
||||
action.data = {'deletion': {'candidates': ['NODE1_ID', 'NODE2_ID']}}
|
||||
action.context = 'action_context'
|
||||
action.action = consts.CLUSTER_RESIZE
|
||||
self.lb_driver.member_remove.return_value = True
|
||||
|
@ -509,7 +621,7 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase):
|
|||
node1.data = {'lb_member': 'MEMBER1_ID'}
|
||||
action = mock.Mock()
|
||||
action.data = {}
|
||||
action.outputs = {'nodes_removed': ['NODE1_ID']}
|
||||
action.data = {'deletion': {'candidates': ['NODE1_ID']}}
|
||||
action.context = 'action_context'
|
||||
action.action = consts.CLUSTER_RESIZE
|
||||
self.lb_driver.member_remove.return_value = False
|
||||
|
|
Loading…
Reference in New Issue