From a0ff3081e257df7978972a20abd6c7c98ab4966e Mon Sep 17 00:00:00 2001 From: Rabi Mishra Date: Wed, 27 May 2020 07:45:23 +0530 Subject: [PATCH] Allow scale-down of ASG as part of update When user wants to do manual scale-up of ASG by increasing the desired_capacity and also update some member properties as part of a stack update, if it fails due to insufficient resources when resizing, trying to manually scale-down by decreasing the desired_capacity won't work, as it would always expect to update the group with the earlier size before scaling down. This patch uses the target_size when building the batches. Task: 39867 Change-Id: Id851530b424f68b5e0e967fe34431483bfffd852 (cherry picked from commit 26d8f64fc90ed9b3ad0413ed0c932fb91b51d666) --- .../aws/autoscaling/autoscaling_group.py | 4 ++ .../openstack/heat/instance_group.py | 20 ++++-- .../openstack/heat/test_instance_group.py | 69 ++++++++++--------- .../heat/test_instance_group_update_policy.py | 1 + 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/heat/engine/resources/aws/autoscaling/autoscaling_group.py b/heat/engine/resources/aws/autoscaling/autoscaling_group.py index 481cca7cdd..189e9fc1fc 100644 --- a/heat/engine/resources/aws/autoscaling/autoscaling_group.py +++ b/heat/engine/resources/aws/autoscaling/autoscaling_group.py @@ -186,6 +186,10 @@ class AutoScalingGroup(cooldown.CooldownMixin, instgrp.InstanceGroup): schema=rolling_update_schema) } + def get_size(self): + """Get desired capacity.""" + return self.properties[self.DESIRED_CAPACITY] + def handle_create(self): return self.create_with_template(self.child_template()) diff --git a/heat/engine/resources/openstack/heat/instance_group.py b/heat/engine/resources/openstack/heat/instance_group.py index 103737d440..3f0678cf61 100644 --- a/heat/engine/resources/openstack/heat/instance_group.py +++ b/heat/engine/resources/openstack/heat/instance_group.py @@ -140,6 +140,10 @@ class InstanceGroup(stack_resource.StackResource): schema=rolling_update_schema) } + def get_size(self): + """Get desired size.""" + return self.properties[self.SIZE] + def validate(self): """Add validation for update_policy.""" self.validate_launchconfig() @@ -335,7 +339,12 @@ class InstanceGroup(stack_resource.StackResource): old_template = group_data.template() capacity = group_data.size(include_failed=True) - batches = list(self._get_batches(capacity, batch_size, min_in_service)) + + target_capacity = min(self.get_size() or capacity, capacity) + + batches = list(self._get_batches(target_capacity, + capacity, batch_size, + min_in_service)) update_timeout = self._update_timeout(len(batches), pause_sec) @@ -359,7 +368,7 @@ class InstanceGroup(stack_resource.StackResource): self._lb_reload() @staticmethod - def _get_batches(capacity, batch_size, min_in_service): + def _get_batches(target_capacity, capacity, batch_size, min_in_service): """Return an iterator over the batches in a batched update. Each batch is a tuple comprising the total size of the group after @@ -368,15 +377,14 @@ class InstanceGroup(stack_resource.StackResource): updating an existing one). """ - efft_capacity = capacity updated = 0 - while rolling_update.needs_update(capacity, efft_capacity, updated): - batch = rolling_update.next_batch(capacity, efft_capacity, + while rolling_update.needs_update(target_capacity, capacity, updated): + batch = rolling_update.next_batch(target_capacity, capacity, updated, batch_size, min_in_service) yield batch - efft_capacity, num_updates = batch + capacity, num_updates = batch updated += num_updates def _check_for_completion(self, updater): diff --git a/heat/tests/openstack/heat/test_instance_group.py b/heat/tests/openstack/heat/test_instance_group.py index c13214c8e5..cfc0de7b1d 100644 --- a/heat/tests/openstack/heat/test_instance_group.py +++ b/heat/tests/openstack/heat/test_instance_group.py @@ -482,42 +482,45 @@ class ResizeWithFailedInstancesTest(InstanceGroupWithNestedStack): class TestGetBatches(common.HeatTestCase): scenarios = [ - ('4_1_0', dict(curr_cap=4, bat_size=1, min_serv=0, - batches=[(4, 1)] * 4)), - ('4_1_4', dict(curr_cap=4, bat_size=1, min_serv=4, - batches=([(5, 1)] * 4) + [(4, 0)])), - ('4_1_5', dict(curr_cap=4, bat_size=1, min_serv=5, - batches=([(5, 1)] * 4) + [(4, 0)])), - ('4_2_0', dict(curr_cap=4, bat_size=2, min_serv=0, - batches=[(4, 2)] * 2)), - ('4_2_4', dict(curr_cap=4, bat_size=2, min_serv=4, - batches=([(6, 2)] * 2) + [(4, 0)])), - ('5_2_0', dict(curr_cap=5, bat_size=2, min_serv=0, - batches=([(5, 2)] * 2) + [(5, 1)])), - ('5_2_4', dict(curr_cap=5, bat_size=2, min_serv=4, - batches=([(6, 2)] * 2) + [(5, 1)])), - ('3_2_0', dict(curr_cap=3, bat_size=2, min_serv=0, - batches=[(3, 2), (3, 1)])), - ('3_2_4', dict(curr_cap=3, bat_size=2, min_serv=4, - batches=[(5, 2), (4, 1), (3, 0)])), - ('4_4_0', dict(curr_cap=4, bat_size=4, min_serv=0, - batches=[(4, 4)])), - ('4_5_0', dict(curr_cap=4, bat_size=5, min_serv=0, - batches=[(4, 4)])), - ('4_4_1', dict(curr_cap=4, bat_size=4, min_serv=1, - batches=[(5, 4), (4, 0)])), - ('4_6_1', dict(curr_cap=4, bat_size=6, min_serv=1, - batches=[(5, 4), (4, 0)])), - ('4_4_2', dict(curr_cap=4, bat_size=4, min_serv=2, - batches=[(6, 4), (4, 0)])), - ('4_4_4', dict(curr_cap=4, bat_size=4, min_serv=4, - batches=[(8, 4), (4, 0)])), - ('4_5_6', dict(curr_cap=4, bat_size=5, min_serv=6, - batches=[(8, 4), (4, 0)])), + ('4_4_1_0', dict(tgt_cap=4, curr_cap=4, bat_size=1, min_serv=0, + batches=[(4, 1)] * 4)), + ('3_4_1_0', dict(tgt_cap=3, curr_cap=4, bat_size=1, min_serv=0, + batches=[(3, 1)] * 3)), + ('4_4_1_4', dict(tgt_cap=4, curr_cap=4, bat_size=1, min_serv=4, + batches=([(5, 1)] * 4) + [(4, 0)])), + ('4_4_1_5', dict(tgt_cap=4, curr_cap=4, bat_size=1, min_serv=5, + batches=([(5, 1)] * 4) + [(4, 0)])), + ('4_4_2_0', dict(tgt_cap=4, curr_cap=4, bat_size=2, min_serv=0, + batches=[(4, 2)] * 2)), + ('4_4_2_4', dict(tgt_cap=4, curr_cap=4, bat_size=2, min_serv=4, + batches=([(6, 2)] * 2) + [(4, 0)])), + ('5_5_2_0', dict(tgt_cap=5, curr_cap=5, bat_size=2, min_serv=0, + batches=([(5, 2)] * 2) + [(5, 1)])), + ('5_5_2_4', dict(tgt_cap=5, curr_cap=5, bat_size=2, min_serv=4, + batches=([(6, 2)] * 2) + [(5, 1)])), + ('3_3_2_0', dict(tgt_cap=3, curr_cap=3, bat_size=2, min_serv=0, + batches=[(3, 2), (3, 1)])), + ('3_3_2_4', dict(tgt_cap=3, curr_cap=3, bat_size=2, min_serv=4, + batches=[(5, 2), (4, 1), (3, 0)])), + ('4_4_4_0', dict(tgt_cap=4, curr_cap=4, bat_size=4, min_serv=0, + batches=[(4, 4)])), + ('4_4_5_0', dict(tgt_cap=4, curr_cap=4, bat_size=5, min_serv=0, + batches=[(4, 4)])), + ('4_4_4_1', dict(tgt_cap=4, curr_cap=4, bat_size=4, min_serv=1, + batches=[(5, 4), (4, 0)])), + ('4_4_6_1', dict(tgt_cap=4, curr_cap=4, bat_size=6, min_serv=1, + batches=[(5, 4), (4, 0)])), + ('4_4_4_2', dict(tgt_cap=4, curr_cap=4, bat_size=4, min_serv=2, + batches=[(6, 4), (4, 0)])), + ('4_4_4_4', dict(tgt_cap=4, curr_cap=4, bat_size=4, min_serv=4, + batches=[(8, 4), (4, 0)])), + ('4_4_5_6', dict(tgt_cap=4, curr_cap=4, bat_size=5, min_serv=6, + batches=[(8, 4), (4, 0)])), ] def test_get_batches(self): - batches = list(instgrp.InstanceGroup._get_batches(self.curr_cap, + batches = list(instgrp.InstanceGroup._get_batches(self.tgt_cap, + self.curr_cap, self.bat_size, self.min_serv)) self.assertEqual(self.batches, batches) diff --git a/heat/tests/openstack/heat/test_instance_group_update_policy.py b/heat/tests/openstack/heat/test_instance_group_update_policy.py index 9ece3aa285..02bb79258b 100644 --- a/heat/tests/openstack/heat/test_instance_group_update_policy.py +++ b/heat/tests/openstack/heat/test_instance_group_update_policy.py @@ -298,5 +298,6 @@ class InstanceGroupReplaceTest(common.HeatTestCase): group = instgrp.InstanceGroup('asg', defn, stack) group._group_data().size = mock.Mock(return_value=12) + group.get_size = mock.Mock(return_value=12) self.assertRaises(ValueError, group._replace, 10, 1, 14 * 60)