diff --git a/heat/common/grouputils.py b/heat/common/grouputils.py index 65ec62f9e..9efb75f8e 100644 --- a/heat/common/grouputils.py +++ b/heat/common/grouputils.py @@ -14,14 +14,15 @@ import six -def get_size(group): +def get_size(group, include_failed=False): """Get number of member resources managed by the specified group. - The list of members are not sorted or returned. + The size exclude failed members default, set include_failed=True + to get total size. """ if group.nested(): resources = [r for r in six.itervalues(group.nested()) - if r.status != r.FAILED] + if include_failed or r.status != r.FAILED] return len(resources) else: return 0 diff --git a/heat/engine/resources/aws/autoscaling_group.py b/heat/engine/resources/aws/autoscaling_group.py index 1433745aa..df7c94ff0 100644 --- a/heat/engine/resources/aws/autoscaling_group.py +++ b/heat/engine/resources/aws/autoscaling_group.py @@ -230,21 +230,18 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin): self.context) self.update_policy = up + self.properties = json_snippet.properties(self.properties_schema, + self.context) if prop_diff: - self.properties = json_snippet.properties(self.properties_schema, - self.context) - # Replace instances first if launch configuration has changed self._try_rolling_update(prop_diff) - if (self.DESIRED_CAPACITY in prop_diff and - self.properties[self.DESIRED_CAPACITY] is not None): - - self.adjust(self.properties[self.DESIRED_CAPACITY], - adjustment_type=EXACT_CAPACITY) - else: - current_capacity = grouputils.get_size(self) - self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY) + if self.properties[self.DESIRED_CAPACITY] is not None: + self.adjust(self.properties[self.DESIRED_CAPACITY], + adjustment_type=EXACT_CAPACITY) + else: + current_capacity = grouputils.get_size(self) + self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY) def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY): """ @@ -263,8 +260,9 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin): new_capacity = _calculate_new_capacity(capacity, adjustment, adjustment_type, lower, upper) - - if new_capacity == capacity: + total = grouputils.get_size(self, include_failed=True) + # if there are failed resources in nested_stack, has to change + if new_capacity == total: LOG.debug('no change in capacity %d' % capacity) return diff --git a/heat/engine/resources/instance_group.py b/heat/engine/resources/instance_group.py index f9e44099f..4d0045f6b 100644 --- a/heat/engine/resources/instance_group.py +++ b/heat/engine/resources/instance_group.py @@ -205,19 +205,19 @@ class InstanceGroup(stack_resource.StackResource): self.context) self.update_policy = up + self.properties = json_snippet.properties(self.properties_schema, + self.context) if prop_diff: - self.properties = json_snippet.properties(self.properties_schema, - self.context) - # Replace instances first if launch configuration has changed self._try_rolling_update(prop_diff) - # Get the current capacity, we may need to adjust if - # Size has changed - if self.SIZE in prop_diff: - curr_size = grouputils.get_size(self) - if curr_size != self.properties[self.SIZE]: - self.resize(self.properties[self.SIZE]) + # Get the current capacity, we may need to adjust if + # Size has changed + if self.properties[self.SIZE] is not None: + self.resize(self.properties[self.SIZE]) + else: + curr_size = grouputils.get_size(self) + self.resize(curr_size) def _tags(self): """ diff --git a/heat/tests/test_autoscaling.py b/heat/tests/test_autoscaling.py index e97e78ed5..d470432b5 100644 --- a/heat/tests/test_autoscaling.py +++ b/heat/tests/test_autoscaling.py @@ -14,6 +14,7 @@ import copy import datetime +import mock import mox from oslo.config import cfg from oslo.utils import timeutils @@ -26,6 +27,7 @@ from heat.common import template_format from heat.engine.notification import autoscaling as notification from heat.engine import parser from heat.engine import resource +from heat.engine.resources.aws import autoscaling_group as asg from heat.engine.resources import instance from heat.engine.resources import loadbalancer from heat.engine.resources.neutron import loadbalancer as neutron_lb @@ -71,6 +73,16 @@ class AutoScalingTest(common.HeatTestCase): cfg.CONF.set_default('heat_waitcondition_server_url', 'http://server.test:8000/v1/waitcondition') self.stub_keystoneclient() + t = template_format.parse(as_template) + stack = utils.parse_stack(t, params=self.params) + self.defn = rsrc_defn.ResourceDefinition( + 'asg', 'AWS::AutoScaling::AutoScalingGroup', + {'AvailabilityZones': ['nova'], + 'LaunchConfigurationName': 'config', + 'MaxSize': 5, + 'MinSize': 1, + 'DesiredCapacity': 2}) + self.asg = asg.AutoScalingGroup('asg', self.defn, stack) def create_scaling_group(self, t, stack, resource_name): # create the launch configuration resource @@ -490,6 +502,15 @@ class AutoScalingTest(common.HeatTestCase): self.m.VerifyAll() + def test_update_in_failed(self): + self.asg.state_set('CREATE', 'FAILED') + # to update the failed asg + self.asg.adjust = mock.Mock(return_value=None) + + self.asg.handle_update(self.defn, None, None) + self.asg.adjust.assert_called_once_with( + 2, adjustment_type='ExactCapacity') + def test_lb_reload_static_resolve(self): t = template_format.parse(as_template) properties = t['Resources']['ElasticLoadBalancer']['Properties'] @@ -710,7 +731,7 @@ class AutoScalingTest(common.HeatTestCase): up_policy.metadata_get().AndReturn(previous_meta) rsrc.metadata_get().AndReturn(previous_meta) - #stub for the metadata accesses while creating the two instances + # stub for the metadata accesses while creating the two instances resource.Resource.metadata_get() resource.Resource.metadata_get() diff --git a/heat/tests/test_instance_group.py b/heat/tests/test_instance_group.py index 02f47ea6f..4fbf8f49d 100644 --- a/heat/tests/test_instance_group.py +++ b/heat/tests/test_instance_group.py @@ -33,12 +33,12 @@ class TestInstanceGroup(common.HeatTestCase): super(TestInstanceGroup, self).setUp() t = template_format.parse(inline_templates.as_template) stack = utils.parse_stack(t, params=inline_templates.as_params) - defn = rsrc_defn.ResourceDefinition( + self.defn = rsrc_defn.ResourceDefinition( 'asg', 'OS::Heat::InstanceGroup', {'Size': 2, 'AvailabilityZones': ['zoneb'], 'LaunchConfigurationName': 'config'}) self.instance_group = instgrp.InstanceGroup('asg', - defn, stack) + self.defn, stack) def test_child_template(self): self.instance_group._create_template = mock.Mock(return_value='tpl') @@ -104,6 +104,14 @@ class TestInstanceGroup(common.HeatTestCase): self.instance_group.create_with_template.assert_called_once_with( '{}', expect_env) + def test_update_in_failed(self): + self.instance_group.state_set('CREATE', 'FAILED') + # to update the failed instance_group + self.instance_group.resize = mock.Mock(return_value=None) + + self.instance_group.handle_update(self.defn, None, None) + self.instance_group.resize.assert_called_once_with(2) + def test_handle_delete(self): self.instance_group.delete_nested = mock.Mock(return_value=None) self.instance_group.handle_delete() @@ -112,8 +120,6 @@ class TestInstanceGroup(common.HeatTestCase): def test_handle_update_size(self): self.instance_group._try_rolling_update = mock.Mock(return_value=None) self.instance_group.resize = mock.Mock(return_value=None) - get_size = self.patchobject(grouputils, 'get_size') - get_size.return_value = 2 props = {'Size': 5} defn = rsrc_defn.ResourceDefinition(