diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index bce14644b..1bf45b1dc 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -836,7 +836,7 @@ class AutoScalingBackend(BaseBackend): end_time=None, recurrence=None, ): - # TODO: Add validations for parameters + max_size = self.make_int(max_size) min_size = self.make_int(min_size) desired_capacity = self.make_int(desired_capacity) @@ -855,6 +855,29 @@ class AutoScalingBackend(BaseBackend): self.scheduled_actions[scheduled_action_name] = scheduled_action return scheduled_action + def describe_scheduled_actions( + self, autoscaling_group_name=None, scheduled_action_names=None + ): + scheduled_actions = [] + for scheduled_action in self.scheduled_actions.values(): + if ( + not autoscaling_group_name + or scheduled_action.name == autoscaling_group_name + ): + if scheduled_action.scheduled_action_name in scheduled_action_names: + scheduled_actions.append(scheduled_action) + elif not scheduled_action_names: + scheduled_actions.append(scheduled_action) + + return scheduled_actions + + def delete_scheduled_action(self, auto_scaling_group_name, scheduled_action_name): + scheduled_action = self.describe_scheduled_actions( + auto_scaling_group_name, scheduled_action_name + ) + if scheduled_action: + self.scheduled_actions.pop(scheduled_action_name, None) + def create_auto_scaling_group( self, name, diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py index da4dbbc02..ebc8901d7 100644 --- a/moto/autoscaling/responses.py +++ b/moto/autoscaling/responses.py @@ -108,7 +108,7 @@ class AutoScalingResponse(BaseResponse): desired_capacity=self._get_int_param("DesiredCapacity"), max_size=self._get_int_param("MaxSize"), min_size=self._get_int_param("MinSize"), - scheduled_action_name=None, + scheduled_action_name=self._get_param("ScheduledActionName"), start_time=self._get_param("StartTime"), end_time=self._get_param("EndTime"), recurrence=self._get_param("Recurrence"), @@ -116,6 +116,24 @@ class AutoScalingResponse(BaseResponse): template = self.response_template(PUT_SCHEDULED_UPDATE_GROUP_ACTION_TEMPLATE) return template.render() + def describe_scheduled_actions(self): + scheduled_actions = self.autoscaling_backend.describe_scheduled_actions( + autoscaling_group_name=self._get_param("AutoScalingGroupName"), + scheduled_action_names=self._get_multi_param("ScheduledActionNames.member"), + ) + template = self.response_template(DESCRIBE_SCHEDULED_ACTIONS) + return template.render(scheduled_actions=scheduled_actions) + + def delete_scheduled_action(self): + auto_scaling_group_name = self._get_param("AutoScalingGroupName") + scheduled_action_name = self._get_param("ScheduledActionName") + self.autoscaling_backend.delete_scheduled_action( + auto_scaling_group_name=auto_scaling_group_name, + scheduled_action_name=scheduled_action_name, + ) + template = self.response_template(DELETE_SCHEDULED_ACTION_TEMPLATE) + return template.render() + @amz_crc32 @amzn_request_id def attach_instances(self): @@ -600,6 +618,38 @@ PUT_SCHEDULED_UPDATE_GROUP_ACTION_TEMPLATE = """ """ +DESCRIBE_SCHEDULED_ACTIONS = """ + + + {% for scheduled_action in scheduled_actions %} + + {{ scheduled_action.name }} + {{ scheduled_action.scheduled_action_name }} + {% if scheduled_action.start_time %} + {{ scheduled_action.start_time }} + {% endif %} + {% if scheduled_action.end_time %} + {{ scheduled_action.end_time }} + {% endif %} + {% if scheduled_action.recurrence %} + {{ scheduled_action.recurrence }} + {% endif %} + {{ scheduled_action.min_size }} + {{ scheduled_action.max_size }} + {{ scheduled_action.desired_capacity }} + + {% endfor %} + + + +""" + +DELETE_SCHEDULED_ACTION_TEMPLATE = """ + + 70a76d42-9665-11e2-9fdf-211deEXAMPLE + +""" + ATTACH_LOAD_BALANCER_TARGET_GROUPS_TEMPLATE = """ diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py index 2ff51561a..9060b243e 100644 --- a/tests/test_autoscaling/test_autoscaling.py +++ b/tests/test_autoscaling/test_autoscaling.py @@ -81,7 +81,7 @@ def test_create_autoscaling_group_within_elb(): StartTime="2022-07-01T00:00:00Z", EndTime="2022-09-01T00:00:00Z", Recurrence="* * * * *", - MinSize=10, + MinSize=5, MaxSize=12, DesiredCapacity=9, ) @@ -124,6 +124,19 @@ def test_create_autoscaling_group_within_elb(): for ec2_instance_id in instances_ids: attached_ids.should.contain(ec2_instance_id) + scheduled_actions = as_client.describe_scheduled_actions( + AutoScalingGroupName="tester_group" + ) + scheduled_action_1 = scheduled_actions["ScheduledUpdateGroupActions"][0] + scheduled_action_1["AutoScalingGroupName"].should.equal("tester_group") + scheduled_action_1["DesiredCapacity"].should.equal(9) + scheduled_action_1["MaxSize"].should.equal(12) + scheduled_action_1["MinSize"].should.equal(5) + scheduled_action_1.should.contain("StartTime") + scheduled_action_1.should.contain("EndTime") + scheduled_action_1["Recurrence"].should.equal("* * * * *") + scheduled_action_1["ScheduledActionName"].should.equal("my-scheduled-action") + @mock_autoscaling def test_create_autoscaling_groups_defaults(): @@ -197,6 +210,85 @@ def test_list_many_autoscaling_groups(): assert "NextToken" not in response2.keys() +@mock_autoscaling +def test_list_many_scheduled_scaling_actions(): + conn = boto3.client("autoscaling", region_name="us-east-1") + + for i in range(30): + conn.put_scheduled_update_group_action( + AutoScalingGroupName="tester_group", + ScheduledActionName=f"my-scheduled-action-{i}", + StartTime=f"2022-07-01T00:00:{i}Z", + EndTime=f"2022-09-01T00:00:{i}Z", + Recurrence="* * * * *", + MinSize=i + 1, + MaxSize=i + 5, + DesiredCapacity=i + 3, + ) + + response = conn.describe_scheduled_actions(AutoScalingGroupName="tester_group") + actions = response["ScheduledUpdateGroupActions"] + actions.should.have.length_of(30) + + +@mock_autoscaling +def test_non_existing_group_name(): + conn = boto3.client("autoscaling", region_name="us-east-1") + + conn.put_scheduled_update_group_action( + AutoScalingGroupName="tester_group", + ScheduledActionName="my-scheduled-action", + StartTime="2022-07-01T00:00:1Z", + EndTime="2022-09-01T00:00:2Z", + Recurrence="* * * * *", + MinSize=1, + MaxSize=5, + DesiredCapacity=3, + ) + + response = conn.describe_scheduled_actions(AutoScalingGroupName="wrong_group") + actions = response["ScheduledUpdateGroupActions"] + actions.should.have.length_of( + 0 + ) # since there is no such group name, no actions have been returned + + +@mock_autoscaling +def test_describe_scheduled_actions_returns_all_actions_when_no_argument_is_passed(): + conn = boto3.client("autoscaling", region_name="us-east-1") + + for i in range(30): + conn.put_scheduled_update_group_action( + AutoScalingGroupName="tester_group", + ScheduledActionName=f"my-scheduled-action-{i}", + StartTime=f"2022-07-01T00:00:{i}Z", + EndTime=f"2022-09-01T00:00:{i}Z", + Recurrence="* * * * *", + MinSize=i + 1, + MaxSize=i + 5, + DesiredCapacity=i + 3, + ) + + for i in range(10): + conn.put_scheduled_update_group_action( + AutoScalingGroupName="tester_group-2", + ScheduledActionName=f"my-scheduled-action-4{i}", + StartTime=f"2022-07-01T00:00:{i}Z", + EndTime=f"2022-09-01T00:00:{i}Z", + Recurrence="* * * * *", + MinSize=i + 1, + MaxSize=i + 5, + DesiredCapacity=i + 3, + ) + + response = conn.describe_scheduled_actions() + actions = response["ScheduledUpdateGroupActions"] + + actions.should.have.length_of( + 40 + ) # Since no argument is passed describe_scheduled_actions, all scheduled actions are returned + + @mock_autoscaling @mock_ec2 def test_propogate_tags(): @@ -261,6 +353,39 @@ def test_autoscaling_group_delete(): ) +@mock_autoscaling +def test_scheduled_action_delete(): + as_client = boto3.client("autoscaling", region_name="us-east-1") + + for i in range(3): + as_client.put_scheduled_update_group_action( + AutoScalingGroupName="tester_group", + ScheduledActionName=f"my-scheduled-action-{i}", + StartTime=f"2022-07-01T00:00:{i}Z", + EndTime=f"2022-09-01T00:00:{i}Z", + Recurrence="* * * * *", + MinSize=i + 1, + MaxSize=i + 5, + DesiredCapacity=i + 3, + ) + + response = as_client.describe_scheduled_actions(AutoScalingGroupName="tester_group") + actions = response["ScheduledUpdateGroupActions"] + actions.should.have.length_of(3) + + as_client.delete_scheduled_action( + AutoScalingGroupName="tester_group", + ScheduledActionName="my-scheduled-action-2", + ) + as_client.delete_scheduled_action( + AutoScalingGroupName="tester_group", + ScheduledActionName="my-scheduled-action-1", + ) + response = as_client.describe_scheduled_actions(AutoScalingGroupName="tester_group") + actions = response["ScheduledUpdateGroupActions"] + actions.should.have.length_of(1) + + @mock_autoscaling @mock_elb def test_describe_load_balancers(): diff --git a/tests/test_autoscaling/test_autoscaling_cloudformation.py b/tests/test_autoscaling/test_autoscaling_cloudformation.py index d5e6cabd5..d95cee074 100644 --- a/tests/test_autoscaling/test_autoscaling_cloudformation.py +++ b/tests/test_autoscaling/test_autoscaling_cloudformation.py @@ -302,6 +302,18 @@ def test_autoscaling_group_with_elb(): ], }, }, + "ScheduledAction": { + "Type": "AWS::AutoScaling::ScheduledAction", + "Properties": { + "AutoScalingGroupName": "test-scaling-group", + "DesiredCapacity": 10, + "EndTime": "2022-08-01T00:00:00Z", + "MaxSize": 15, + "MinSize": 5, + "Recurrence": "* * * * *", + "StartTime": "2022-07-01T00:00:00Z", + }, + }, "my-launch-config": { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { @@ -387,6 +399,12 @@ def test_autoscaling_group_with_elb(): tag_keys.should.contain("propagated-test-tag") tag_keys.should_not.contain("not-propagated-test-tag") + # confirm scheduled scaling action was created + response = client.describe_scheduled_actions( + AutoScalingGroupName="test-scaling-group" + )["ScheduledUpdateGroupActions"] + response.should.have.length_of(1) + @mock_autoscaling @mock_cloudformation