From f433cfc73e5e11275923d1ac5e202719a7906f03 Mon Sep 17 00:00:00 2001 From: tengqm Date: Tue, 23 Dec 2014 14:20:49 +0800 Subject: [PATCH] Move LB reload logic into scaling library The lb_reload() logic is moved into a separate module of the scaling library. A new test case is added to test this utility function. Since the revised _lb_reload() method checks if LOAD_BALANCER_NAMES is defined/provided or not, the OpenStack version of AutoScalingGroup doesn't have to implement an empty _lb_reload() method now. partial-blueprint: as-lib partial-blueprint: reorg-asg-code Change-Id: I7387d0f27c6121be29d9a035be4cca0b9bbdf441 --- heat/engine/resources/instance_group.py | 39 +---- .../resources/openstack/autoscaling_group.py | 6 - heat/scaling/lbutils.py | 51 +++++++ heat/tests/autoscaling/test_lbutils.py | 142 ++++++++++++++++++ 4 files changed, 198 insertions(+), 40 deletions(-) create mode 100644 heat/scaling/lbutils.py create mode 100644 heat/tests/autoscaling/test_lbutils.py diff --git a/heat/engine/resources/instance_group.py b/heat/engine/resources/instance_group.py index f9e44099f4..382222c4e9 100644 --- a/heat/engine/resources/instance_group.py +++ b/heat/engine/resources/instance_group.py @@ -11,10 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - from heat.common import environment_format -from heat.common import exception from heat.common import grouputils from heat.common.i18n import _ from heat.common import timeutils as iso8601utils @@ -24,6 +21,7 @@ from heat.engine import properties from heat.engine import rsrc_defn from heat.engine import scheduler from heat.engine import stack_resource +from heat.scaling import lbutils from heat.scaling import template @@ -348,37 +346,10 @@ class InstanceGroup(stack_resource.StackResource): self._lb_reload() def _lb_reload(self, exclude=None): - ''' - Notify the LoadBalancer to reload its config to include - the changes in instances we have just made. - - This must be done after activation (instance in ACTIVE state), - otherwise the instances' IP addresses may not be available. - ''' - exclude = exclude or [] - if self.properties[self.LOAD_BALANCER_NAMES]: - id_list = grouputils.get_member_refids(self, exclude=exclude) - for lb in self.properties[self.LOAD_BALANCER_NAMES]: - lb_resource = self.stack[lb] - - props = copy.copy(lb_resource.properties.data) - if 'Instances' in lb_resource.properties_schema: - props['Instances'] = id_list - elif 'members' in lb_resource.properties_schema: - props['members'] = id_list - else: - raise exception.Error( - _("Unsupported resource '%s' in LoadBalancerNames") % - (lb,)) - - lb_defn = rsrc_defn.ResourceDefinition( - lb_resource.name, - lb_resource.type(), - properties=props, - metadata=lb_resource.t.get('Metadata'), - deletion_policy=lb_resource.t.get('DeletionPolicy')) - - scheduler.TaskRunner(lb_resource.update, lb_defn)() + lb_names = self.properties.get(self.LOAD_BALANCER_NAMES, None) + if lb_names: + lb_dict = dict((name, self.stack[name]) for name in lb_names) + lbutils.reload_loadbalancers(self, lb_dict, exclude) def FnGetRefId(self): return self.physical_resource_name_or_FnGetRefId() diff --git a/heat/engine/resources/openstack/autoscaling_group.py b/heat/engine/resources/openstack/autoscaling_group.py index 31ac842318..6261d26118 100644 --- a/heat/engine/resources/openstack/autoscaling_group.py +++ b/heat/engine/resources/openstack/autoscaling_group.py @@ -125,12 +125,6 @@ class AutoScalingResourceGroup(aws_asg.AutoScalingGroup): properties=rsrc.get('properties'), metadata=rsrc.get('metadata')) - def _lb_reload(self, exclude=None): - """AutoScalingResourceGroup does not maintain load balancer - connections, so we just ignore calls to update the LB. - """ - pass - def _try_rolling_update(self, prop_diff): if (self.properties[self.ROLLING_UPDATES] and self.RESOURCE in prop_diff): diff --git a/heat/scaling/lbutils.py b/heat/scaling/lbutils.py new file mode 100644 index 0000000000..58006bb7e0 --- /dev/null +++ b/heat/scaling/lbutils.py @@ -0,0 +1,51 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +import six + +from heat.common import exception +from heat.common import grouputils +from heat.common.i18n import _ +from heat.engine import rsrc_defn +from heat.engine import scheduler + + +def reload_loadbalancers(group, load_balancers, exclude=None): + ''' + Notify the LoadBalancer to reload its config. + + This must be done after activation (instance in ACTIVE state), otherwise + the instances' IP addresses may not be available. + ''' + exclude = exclude or [] + id_list = grouputils.get_member_refids(group, exclude=exclude) + for name, lb in six.iteritems(load_balancers): + props = copy.copy(lb.properties.data) + if 'Instances' in lb.properties_schema: + props['Instances'] = id_list + elif 'members' in lb.properties_schema: + props['members'] = id_list + else: + raise exception.Error( + _("Unsupported resource '%s' in LoadBalancerNames") % name) + + lb_defn = rsrc_defn.ResourceDefinition( + lb.name, + lb.type(), + properties=props, + metadata=lb.t.get('Metadata'), + deletion_policy=lb.t.get('DeletionPolicy')) + + scheduler.TaskRunner(lb.update, lb_defn)() diff --git a/heat/tests/autoscaling/test_lbutils.py b/heat/tests/autoscaling/test_lbutils.py new file mode 100644 index 0000000000..f4a0b3510d --- /dev/null +++ b/heat/tests/autoscaling/test_lbutils.py @@ -0,0 +1,142 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import six + +from heat.common import exception +from heat.common import grouputils +from heat.common import template_format +from heat.engine import properties +from heat.engine import resource +from heat.scaling import lbutils +from heat.tests import common +from heat.tests import generic_resource +from heat.tests import utils + +lb_stack = ''' +heat_template_version: 2013-05-23 +resources: + neutron_lb_1: + type: Mock::Neutron::LB + neutron_lb_2: + type: Mock::Neutron::LB + aws_lb_1: + type: Mock::AWS::LB + aws_lb_2: + type: Mock::AWS::LB + non_lb: + type: Mock::Not::LB +''' + + +class MockedNeutronLB(generic_resource.GenericResource): + properties_schema = { + 'members': properties.Schema( + properties.Schema.LIST, + update_allowed=True) + } + + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + return + + +class MockedAWSLB(generic_resource.GenericResource): + properties_schema = { + 'Instances': properties.Schema( + properties.Schema.LIST, + update_allowed=True) + } + + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + return + + +class LBUtilsTest(common.HeatTestCase): + # need to mock a group so that group_utils.get_member_refids will work + # load_balancers is a dict of load_balancer objects, where each lb has + # properties for checking + + def setUp(self): + super(LBUtilsTest, self).setUp() + resource._register_class('Mock::Neutron::LB', MockedNeutronLB) + resource._register_class('Mock::AWS::LB', MockedAWSLB) + resource._register_class('Mock::Not::LB', + generic_resource.GenericResource) + + t = template_format.parse(lb_stack) + self.stack = utils.parse_stack(t) + + def test_reload_aws_lb(self): + group = mock.Mock() + self.patchobject(grouputils, 'get_member_refids', + return_value=['ID1', 'ID2', 'ID3']) + + lb1 = self.stack['aws_lb_1'] + lb2 = self.stack['aws_lb_2'] + lbs = { + 'LB_1': lb1, + 'LB_2': lb2 + } + + lb1.handle_update = mock.Mock() + lb2.handle_update = mock.Mock() + prop_diff = {'Instances': ['ID1', 'ID2', 'ID3']} + + lbutils.reload_loadbalancers(group, lbs) + + # For verification's purpose, we just check the prop_diff + lb1.handle_update.assert_called_with(mock.ANY, mock.ANY, + prop_diff) + lb2.handle_update.assert_called_with(mock.ANY, mock.ANY, + prop_diff) + + def test_reload_neutron_lb(self): + group = mock.Mock() + self.patchobject(grouputils, 'get_member_refids', + return_value=['ID1', 'ID2', 'ID3']) + + lb1 = self.stack['neutron_lb_1'] + lb2 = self.stack['neutron_lb_2'] + lbs = { + 'LB_1': lb1, + 'LB_2': lb2 + } + + lb1.handle_update = mock.Mock() + lb2.handle_update = mock.Mock() + + prop_diff = {'members': ['ID1', 'ID2', 'ID3']} + + lbutils.reload_loadbalancers(group, lbs) + + # For verification's purpose, we just check the prop_diff + lb1.handle_update.assert_called_with(mock.ANY, mock.ANY, + prop_diff) + lb2.handle_update.assert_called_with(mock.ANY, mock.ANY, + prop_diff) + + def test_reload_non_lb(self): + group = mock.Mock() + self.patchobject(grouputils, 'get_member_refids', + return_value=['ID1', 'ID2', 'ID3']) + + lbs = { + 'LB_1': self.stack['non_lb'], + } + + error = self.assertRaises(exception.Error, + lbutils.reload_loadbalancers, + group, lbs) + self.assertIn("Unsupported resource 'LB_1' in LoadBalancerNames", + six.text_type(error))