diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index b1d83ba64..508e991f8 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -945,7 +945,7 @@ - [ ] put_scheduled_update_group_action - [ ] put_warm_pool - [ ] record_lifecycle_action_heartbeat -- [ ] resume_processes +- [X] resume_processes - [X] set_desired_capacity - [X] set_instance_health - [X] set_instance_protection diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index 2ed874ed2..f90c5de5e 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -1040,8 +1040,31 @@ class AutoScalingBackend(BaseBackend): self.elbv2_backend.deregister_targets(target_group, (asg_targets)) def suspend_processes(self, group_name, scaling_processes): + all_proc_names = [ + "Launch", + "Terminate", + "AddToLoadBalancer", + "AlarmNotification", + "AZRebalance", + "HealthCheck", + "InstanceRefresh", + "ReplaceUnhealthy", + "ScheduledActions", + ] group = self.autoscaling_groups[group_name] - group.suspended_processes = scaling_processes or [] + set_to_add = set(scaling_processes or all_proc_names) + group.suspended_processes = list( + set(group.suspended_processes).union(set_to_add) + ) + + def resume_processes(self, group_name, scaling_processes): + group = self.autoscaling_groups[group_name] + if scaling_processes: + group.suspended_processes = list( + set(group.suspended_processes).difference(set(scaling_processes)) + ) + else: + group.suspended_processes = [] def set_instance_protection( self, group_name, instance_ids, protected_from_scale_in diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py index e6a309d6f..12d32d99c 100644 --- a/moto/autoscaling/responses.py +++ b/moto/autoscaling/responses.py @@ -359,6 +359,15 @@ class AutoScalingResponse(BaseResponse): template = self.response_template(SUSPEND_PROCESSES_TEMPLATE) return template.render() + def resume_processes(self): + autoscaling_group_name = self._get_param("AutoScalingGroupName") + scaling_processes = self._get_multi_param("ScalingProcesses.member") + self.autoscaling_backend.resume_processes( + autoscaling_group_name, scaling_processes + ) + template = self.response_template(RESUME_PROCESSES_TEMPLATE) + return template.render() + def set_instance_protection(self): group_name = self._get_param("AutoScalingGroupName") instance_ids = self._get_multi_param("InstanceIds.member") @@ -802,6 +811,12 @@ SUSPEND_PROCESSES_TEMPLATE = """ + + + +""" + SET_INSTANCE_HEALTH_TEMPLATE = """ diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py index 22788f8e4..77bbec09a 100644 --- a/tests/test_autoscaling/test_autoscaling.py +++ b/tests/test_autoscaling/test_autoscaling.py @@ -2391,6 +2391,146 @@ def test_suspend_processes(): assert launch_suspended is True +@mock_autoscaling +def test_suspend_processes_all_by_default(): + mocked_networking = setup_networking() + client = boto3.client("autoscaling", region_name="us-east-1") + client.create_launch_configuration( + LaunchConfigurationName="lc", ImageId=EXAMPLE_AMI_ID, InstanceType="t2.medium" + ) + client.create_auto_scaling_group( + LaunchConfigurationName="lc", + AutoScalingGroupName="test-asg", + MinSize=1, + MaxSize=1, + VPCZoneIdentifier=mocked_networking["subnet1"], + ) + + # When we suspend with no processes specified + client.suspend_processes(AutoScalingGroupName="test-asg") + + res = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test-asg"]) + + # All processes should be suspended + all_proc_names = [ + "Launch", + "Terminate", + "AddToLoadBalancer", + "AlarmNotification", + "AZRebalance", + "HealthCheck", + "InstanceRefresh", + "ReplaceUnhealthy", + "ScheduledActions", + ] + suspended_proc_names = [ + proc["ProcessName"] + for proc in res["AutoScalingGroups"][0]["SuspendedProcesses"] + ] + set(suspended_proc_names).should.equal(set(all_proc_names)) + + +@mock_autoscaling +def test_suspend_additional_processes(): + mocked_networking = setup_networking() + client = boto3.client("autoscaling", region_name="us-east-1") + client.create_launch_configuration( + LaunchConfigurationName="lc", ImageId=EXAMPLE_AMI_ID, InstanceType="t2.medium" + ) + client.create_auto_scaling_group( + LaunchConfigurationName="lc", + AutoScalingGroupName="test-asg", + MinSize=1, + MaxSize=1, + VPCZoneIdentifier=mocked_networking["subnet1"], + ) + + # When we suspend the 'Launch' and 'Terminate' processes in separate calls + client.suspend_processes( + AutoScalingGroupName="test-asg", ScalingProcesses=["Launch"] + ) + client.suspend_processes( + AutoScalingGroupName="test-asg", ScalingProcesses=["Terminate"] + ) + + res = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test-asg"]) + + # Both 'Launch' and 'Terminate' should be suspended + launch_suspended = False + terminate_suspended = False + for proc in res["AutoScalingGroups"][0]["SuspendedProcesses"]: + if proc.get("ProcessName") == "Launch": + launch_suspended = True + if proc.get("ProcessName") == "Terminate": + terminate_suspended = True + + assert launch_suspended is True + assert terminate_suspended is True + + +@mock_autoscaling +def test_resume_processes(): + mocked_networking = setup_networking() + client = boto3.client("autoscaling", region_name="us-east-1") + client.create_launch_configuration( + LaunchConfigurationName="lc", ImageId=EXAMPLE_AMI_ID, InstanceType="t2.medium" + ) + client.create_auto_scaling_group( + LaunchConfigurationName="lc", + AutoScalingGroupName="test-asg", + MinSize=1, + MaxSize=1, + VPCZoneIdentifier=mocked_networking["subnet1"], + ) + + # When we suspect 'Launch' and 'Termiate' process then resume 'Launch' + client.suspend_processes( + AutoScalingGroupName="test-asg", ScalingProcesses=["Launch", "Terminate"] + ) + + client.resume_processes( + AutoScalingGroupName="test-asg", ScalingProcesses=["Launch"] + ) + + res = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test-asg"]) + + # Only 'Terminate' should be suspended + expected_suspended_processes = [ + {"ProcessName": "Terminate", "SuspensionReason": ""} + ] + res["AutoScalingGroups"][0]["SuspendedProcesses"].should.equal( + expected_suspended_processes + ) + + +@mock_autoscaling +def test_resume_processes_all_by_default(): + mocked_networking = setup_networking() + client = boto3.client("autoscaling", region_name="us-east-1") + client.create_launch_configuration( + LaunchConfigurationName="lc", ImageId=EXAMPLE_AMI_ID, InstanceType="t2.medium" + ) + client.create_auto_scaling_group( + LaunchConfigurationName="lc", + AutoScalingGroupName="test-asg", + MinSize=1, + MaxSize=1, + VPCZoneIdentifier=mocked_networking["subnet1"], + ) + + # When we suspend two processes then resume with no process argument + client.suspend_processes( + AutoScalingGroupName="test-asg", ScalingProcesses=["Launch", "Terminate"] + ) + + client.resume_processes(AutoScalingGroupName="test-asg") + + res = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test-asg"]) + + # No processes should be suspended + res["AutoScalingGroups"][0]["SuspendedProcesses"].should.equal([]) + + @mock_autoscaling def test_set_instance_protection(): mocked_networking = setup_networking()