Correct group's updates from a failed state

This patch modifies autoscaling/instance group to
handle updates from a failed state.

Change-Id: I235b3e97276361022e708266bf06bca6556bcf76
Closes-Bug: #1383618
This commit is contained in:
huangtianhua 2014-11-19 15:12:51 +08:00
parent c16f539c53
commit 1eabdaf45f
5 changed files with 56 additions and 30 deletions

View File

@ -14,14 +14,15 @@
import six import six
def get_size(group): def get_size(group, include_failed=False):
"""Get number of member resources managed by the specified group. """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(): if group.nested():
resources = [r for r in six.itervalues(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) return len(resources)
else: else:
return 0 return 0

View File

@ -230,21 +230,18 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
self.context) self.context)
self.update_policy = up self.update_policy = up
self.properties = json_snippet.properties(self.properties_schema,
self.context)
if prop_diff: if prop_diff:
self.properties = json_snippet.properties(self.properties_schema,
self.context)
# Replace instances first if launch configuration has changed # Replace instances first if launch configuration has changed
self._try_rolling_update(prop_diff) self._try_rolling_update(prop_diff)
if (self.DESIRED_CAPACITY in prop_diff and if self.properties[self.DESIRED_CAPACITY] is not None:
self.properties[self.DESIRED_CAPACITY] is not None): self.adjust(self.properties[self.DESIRED_CAPACITY],
adjustment_type=EXACT_CAPACITY)
self.adjust(self.properties[self.DESIRED_CAPACITY], else:
adjustment_type=EXACT_CAPACITY) current_capacity = grouputils.get_size(self)
else: self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY)
current_capacity = grouputils.get_size(self)
self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY)
def adjust(self, adjustment, adjustment_type=CHANGE_IN_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, new_capacity = _calculate_new_capacity(capacity, adjustment,
adjustment_type, lower, upper) adjustment_type, lower, upper)
total = grouputils.get_size(self, include_failed=True)
if new_capacity == capacity: # if there are failed resources in nested_stack, has to change
if new_capacity == total:
LOG.debug('no change in capacity %d' % capacity) LOG.debug('no change in capacity %d' % capacity)
return return

View File

@ -205,19 +205,19 @@ class InstanceGroup(stack_resource.StackResource):
self.context) self.context)
self.update_policy = up self.update_policy = up
self.properties = json_snippet.properties(self.properties_schema,
self.context)
if prop_diff: if prop_diff:
self.properties = json_snippet.properties(self.properties_schema,
self.context)
# Replace instances first if launch configuration has changed # Replace instances first if launch configuration has changed
self._try_rolling_update(prop_diff) self._try_rolling_update(prop_diff)
# Get the current capacity, we may need to adjust if # Get the current capacity, we may need to adjust if
# Size has changed # Size has changed
if self.SIZE in prop_diff: if self.properties[self.SIZE] is not None:
curr_size = grouputils.get_size(self) self.resize(self.properties[self.SIZE])
if curr_size != self.properties[self.SIZE]: else:
self.resize(self.properties[self.SIZE]) curr_size = grouputils.get_size(self)
self.resize(curr_size)
def _tags(self): def _tags(self):
""" """

View File

@ -14,6 +14,7 @@
import copy import copy
import datetime import datetime
import mock
import mox import mox
from oslo.config import cfg from oslo.config import cfg
from oslo.utils import timeutils 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.notification import autoscaling as notification
from heat.engine import parser from heat.engine import parser
from heat.engine import resource 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 instance
from heat.engine.resources import loadbalancer from heat.engine.resources import loadbalancer
from heat.engine.resources.neutron import loadbalancer as neutron_lb 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', cfg.CONF.set_default('heat_waitcondition_server_url',
'http://server.test:8000/v1/waitcondition') 'http://server.test:8000/v1/waitcondition')
self.stub_keystoneclient() 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): def create_scaling_group(self, t, stack, resource_name):
# create the launch configuration resource # create the launch configuration resource
@ -490,6 +502,15 @@ class AutoScalingTest(common.HeatTestCase):
self.m.VerifyAll() 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): def test_lb_reload_static_resolve(self):
t = template_format.parse(as_template) t = template_format.parse(as_template)
properties = t['Resources']['ElasticLoadBalancer']['Properties'] properties = t['Resources']['ElasticLoadBalancer']['Properties']
@ -710,7 +731,7 @@ class AutoScalingTest(common.HeatTestCase):
up_policy.metadata_get().AndReturn(previous_meta) up_policy.metadata_get().AndReturn(previous_meta)
rsrc.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()
resource.Resource.metadata_get() resource.Resource.metadata_get()

View File

@ -33,12 +33,12 @@ class TestInstanceGroup(common.HeatTestCase):
super(TestInstanceGroup, self).setUp() super(TestInstanceGroup, self).setUp()
t = template_format.parse(inline_templates.as_template) t = template_format.parse(inline_templates.as_template)
stack = utils.parse_stack(t, params=inline_templates.as_params) stack = utils.parse_stack(t, params=inline_templates.as_params)
defn = rsrc_defn.ResourceDefinition( self.defn = rsrc_defn.ResourceDefinition(
'asg', 'OS::Heat::InstanceGroup', 'asg', 'OS::Heat::InstanceGroup',
{'Size': 2, 'AvailabilityZones': ['zoneb'], {'Size': 2, 'AvailabilityZones': ['zoneb'],
'LaunchConfigurationName': 'config'}) 'LaunchConfigurationName': 'config'})
self.instance_group = instgrp.InstanceGroup('asg', self.instance_group = instgrp.InstanceGroup('asg',
defn, stack) self.defn, stack)
def test_child_template(self): def test_child_template(self):
self.instance_group._create_template = mock.Mock(return_value='tpl') 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( self.instance_group.create_with_template.assert_called_once_with(
'{}', expect_env) '{}', 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): def test_handle_delete(self):
self.instance_group.delete_nested = mock.Mock(return_value=None) self.instance_group.delete_nested = mock.Mock(return_value=None)
self.instance_group.handle_delete() self.instance_group.handle_delete()
@ -112,8 +120,6 @@ class TestInstanceGroup(common.HeatTestCase):
def test_handle_update_size(self): def test_handle_update_size(self):
self.instance_group._try_rolling_update = mock.Mock(return_value=None) self.instance_group._try_rolling_update = mock.Mock(return_value=None)
self.instance_group.resize = 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} props = {'Size': 5}
defn = rsrc_defn.ResourceDefinition( defn = rsrc_defn.ResourceDefinition(