diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index c3b0fa985..e06575724 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -368,6 +368,8 @@ class FakeAutoScalingGroup(CloudFormationModel): autoscaling_backend, ec2_backend, tags, + mixed_instance_policy, + capacity_rebalance, new_instances_protected_from_scale_in=False, ): self.autoscaling_backend = autoscaling_backend @@ -376,6 +378,7 @@ class FakeAutoScalingGroup(CloudFormationModel): self._id = str(uuid4()) self.region = self.autoscaling_backend.region_name self.account_id = self.autoscaling_backend.account_id + self.service_linked_role = f"arn:aws:iam::{self.account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling" self._set_azs_and_vpcs(availability_zones, vpc_zone_identifier) @@ -385,7 +388,10 @@ class FakeAutoScalingGroup(CloudFormationModel): self.launch_template = None self.launch_config = None - self._set_launch_configuration(launch_config_name, launch_template) + self._set_launch_configuration( + launch_config_name, launch_template, mixed_instance_policy + ) + self.mixed_instance_policy = mixed_instance_policy self.default_cooldown = ( default_cooldown if default_cooldown else DEFAULT_COOLDOWN @@ -395,6 +401,7 @@ class FakeAutoScalingGroup(CloudFormationModel): self.load_balancers = load_balancers self.target_group_arns = target_group_arns self.placement_group = placement_group + self.capacity_rebalance = capacity_rebalance self.termination_policies = termination_policies or ["Default"] self.new_instances_protected_from_scale_in = ( new_instances_protected_from_scale_in @@ -458,16 +465,29 @@ class FakeAutoScalingGroup(CloudFormationModel): self.availability_zones = availability_zones self.vpc_zone_identifier = vpc_zone_identifier - def _set_launch_configuration(self, launch_config_name, launch_template): + def _set_launch_configuration( + self, launch_config_name, launch_template, mixed_instance_policy + ): if launch_config_name: self.launch_config = self.autoscaling_backend.launch_configurations[ launch_config_name ] self.launch_config_name = launch_config_name - if launch_template: - launch_template_id = launch_template.get("launch_template_id") - launch_template_name = launch_template.get("launch_template_name") + if launch_template or mixed_instance_policy: + if launch_template: + launch_template_id = launch_template.get("launch_template_id") + launch_template_name = launch_template.get("launch_template_name") + self.launch_template_version = ( + launch_template.get("version") or "$Default" + ) + else: + spec = mixed_instance_policy["LaunchTemplate"][ + "LaunchTemplateSpecification" + ] + launch_template_id = spec.get("LaunchTemplateId") + launch_template_name = spec.get("LaunchTemplateName") + self.launch_template_version = spec.get("Version") or "$Default" if not (launch_template_id or launch_template_name) or ( launch_template_id and launch_template_name @@ -484,7 +504,6 @@ class FakeAutoScalingGroup(CloudFormationModel): self.launch_template = self.ec2_backend.get_launch_template_by_name( launch_template_name ) - self.launch_template_version = launch_template.get("version") or "$Default" @staticmethod def __set_string_propagate_at_launch_booleans_on_tags(tags): @@ -637,7 +656,9 @@ class FakeAutoScalingGroup(CloudFormationModel): if max_size is not None and max_size < len(self.instance_states): desired_capacity = max_size - self._set_launch_configuration(launch_config_name, launch_template) + self._set_launch_configuration( + launch_config_name, launch_template, mixed_instance_policy=None + ) if health_check_period is not None: self.health_check_period = health_check_period @@ -909,8 +930,10 @@ class AutoScalingBackend(BaseBackend): placement_group, termination_policies, tags, + capacity_rebalance=False, new_instances_protected_from_scale_in=False, instance_id=None, + mixed_instance_policy=None, ): max_size = self.make_int(max_size) min_size = self.make_int(min_size) @@ -921,9 +944,13 @@ class AutoScalingBackend(BaseBackend): else: health_check_period = self.make_int(health_check_period) - # TODO: Add MixedInstancesPolicy once implemented. # Verify only a single launch config-like parameter is provided. - params = [launch_config_name, launch_template, instance_id] + params = [ + launch_config_name, + launch_template, + instance_id, + mixed_instance_policy, + ] num_params = sum([1 for param in params if param]) if num_params != 1: @@ -962,6 +989,8 @@ class AutoScalingBackend(BaseBackend): ec2_backend=self.ec2_backend, tags=tags, new_instances_protected_from_scale_in=new_instances_protected_from_scale_in, + mixed_instance_policy=mixed_instance_policy, + capacity_rebalance=capacity_rebalance, ) self.autoscaling_groups[name] = group diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py index 93c401f3d..e6e355111 100644 --- a/moto/autoscaling/responses.py +++ b/moto/autoscaling/responses.py @@ -80,6 +80,7 @@ class AutoScalingResponse(BaseResponse): return template.render() def create_auto_scaling_group(self): + params = self._get_params() self.autoscaling_backend.create_auto_scaling_group( name=self._get_param("AutoScalingGroupName"), availability_zones=self._get_multi_param("AvailabilityZones.member"), @@ -89,6 +90,7 @@ class AutoScalingResponse(BaseResponse): instance_id=self._get_param("InstanceId"), launch_config_name=self._get_param("LaunchConfigurationName"), launch_template=self._get_dict_param("LaunchTemplate."), + mixed_instance_policy=params.get("MixedInstancesPolicy"), vpc_zone_identifier=self._get_param("VPCZoneIdentifier"), default_cooldown=self._get_int_param("DefaultCooldown"), health_check_period=self._get_int_param("HealthCheckGracePeriod"), @@ -98,6 +100,7 @@ class AutoScalingResponse(BaseResponse): placement_group=self._get_param("PlacementGroup"), termination_policies=self._get_multi_param("TerminationPolicies.member"), tags=self._get_list_prefix("Tags.member"), + capacity_rebalance=self._get_bool_param("CapacityRebalance"), new_instances_protected_from_scale_in=self._get_bool_param( "NewInstancesProtectedFromScaleIn", False ), @@ -748,6 +751,30 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """2013-05-06T17:47:15.107Z {% if group.launch_config_name %} {{ group.launch_config_name }} + {% elif group.mixed_instance_policy %} + + + + {{ group.launch_template.id }} + {{ group.launch_template_version }} + {{ group.launch_template.name }} + + {% if group.mixed_instance_policy.get("LaunchTemplate", {}).get("Overrides", []) %} + + {% for member in group.mixed_instance_policy.get("LaunchTemplate", {}).get("Overrides", []) %} + + {% if member.get("InstanceType") %} + {{ member.get("InstanceType") }} + {% endif %} + {% if member.get("WeightedCapacity") %} + {{ member.get("WeightedCapacity") }} + {% endif %} + + {% endfor %} + + {% endif %} + + {% elif group.launch_template %} {{ group.launch_template.id }} @@ -777,6 +804,7 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """ {{ group.desired_capacity }} + {{ 'true' if group.capacity_rebalance else 'false' }} {% for availability_zone in group.availability_zones %} {{ availability_zone }} @@ -833,6 +861,7 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """ {% endif %} + {{ group.service_linked_role }} {% endfor %} diff --git a/tests/terraformtests/terraform-tests.success.txt b/tests/terraformtests/terraform-tests.success.txt index 87f270543..6a2aaa3d1 100644 --- a/tests/terraformtests/terraform-tests.success.txt +++ b/tests/terraformtests/terraform-tests.success.txt @@ -11,6 +11,14 @@ autoscaling: - TestAccAutoScalingAttachment - TestAccAutoScalingGroupDataSource - TestAccAutoScalingGroupTag + - TestAccAutoScalingGroup_basic + - TestAccAutoScalingGroup_disappears + - TestAccAutoScalingGroup_nameGenerated + - TestAccAutoScalingGroup_namePrefix + - TestAccAutoScalingGroup_enablingMetrics + - TestAccAutoScalingGroup_suspendingProcesses + - TestAccAutoScalingGroup_mixedInstancesPolicy + - TestAccAutoScalingGroup_MixedInstancesPolicy_capacityRebalance - TestAccAutoScalingLaunchConfigurationDataSource - TestAccAutoScalingLaunchConfiguration_ batch: diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py index 7e45b42c7..2ea05c872 100644 --- a/tests/test_autoscaling/test_autoscaling.py +++ b/tests/test_autoscaling/test_autoscaling.py @@ -61,22 +61,6 @@ def test_create_autoscaling_group_from_instance(): MinSize=1, MaxSize=3, DesiredCapacity=2, - Tags=[ - { - "ResourceId": "test_asg", - "ResourceType": "auto-scaling-group", - "Key": "propogated-tag-key", - "Value": "propagate-tag-value", - "PropagateAtLaunch": True, - }, - { - "ResourceId": "test_asg", - "ResourceType": "auto-scaling-group", - "Key": "not-propogated-tag-key", - "Value": "not-propagate-tag-value", - "PropagateAtLaunch": False, - }, - ], VPCZoneIdentifier=mocked_instance_with_networking["subnet1"], NewInstancesProtectedFromScaleIn=False, ) @@ -874,6 +858,116 @@ def test_create_autoscaling_policy_with_predictive_scaling_config(): ) +@mock_autoscaling +@mock_ec2 +def test_create_auto_scaling_group_with_mixed_instances_policy(): + mocked_networking = setup_networking(region_name="eu-west-1") + client = boto3.client("autoscaling", region_name="eu-west-1") + ec2_client = boto3.client("ec2", region_name="eu-west-1") + asg_name = "asg_test" + + lt = ec2_client.create_launch_template( + LaunchTemplateName="launchie", + LaunchTemplateData={"ImageId": EXAMPLE_AMI_ID}, + )["LaunchTemplate"] + client.create_auto_scaling_group( + MixedInstancesPolicy={ + "LaunchTemplate": { + "LaunchTemplateSpecification": { + "LaunchTemplateName": "launchie", + "Version": "$DEFAULT", + } + } + }, + AutoScalingGroupName=asg_name, + MinSize=2, + MaxSize=2, + VPCZoneIdentifier=mocked_networking["subnet1"], + ) + + # Assert we can describe MixedInstancesPolicy + response = client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name]) + group = response["AutoScalingGroups"][0] + group.should.have.key("MixedInstancesPolicy").equals( + { + "LaunchTemplate": { + "LaunchTemplateSpecification": { + "LaunchTemplateId": lt["LaunchTemplateId"], + "LaunchTemplateName": "launchie", + "Version": "$DEFAULT", + } + } + } + ) + + # Assert the LaunchTemplate is known for the resulting instances + response = client.describe_auto_scaling_instances() + len(response["AutoScalingInstances"]).should.equal(2) + for instance in response["AutoScalingInstances"]: + instance["LaunchTemplate"].should.equal( + { + "LaunchTemplateId": lt["LaunchTemplateId"], + "LaunchTemplateName": "launchie", + "Version": "$DEFAULT", + } + ) + + +@mock_autoscaling +@mock_ec2 +def test_create_auto_scaling_group_with_mixed_instances_policy_overrides(): + mocked_networking = setup_networking(region_name="eu-west-1") + client = boto3.client("autoscaling", region_name="eu-west-1") + ec2_client = boto3.client("ec2", region_name="eu-west-1") + asg_name = "asg_test" + + lt = ec2_client.create_launch_template( + LaunchTemplateName="launchie", + LaunchTemplateData={"ImageId": EXAMPLE_AMI_ID}, + )["LaunchTemplate"] + client.create_auto_scaling_group( + MixedInstancesPolicy={ + "LaunchTemplate": { + "LaunchTemplateSpecification": { + "LaunchTemplateName": "launchie", + "Version": "$DEFAULT", + }, + "Overrides": [ + { + "InstanceType": "t2.medium", + "WeightedCapacity": "50", + } + ], + } + }, + AutoScalingGroupName=asg_name, + MinSize=2, + MaxSize=2, + VPCZoneIdentifier=mocked_networking["subnet1"], + ) + + # Assert we can describe MixedInstancesPolicy + response = client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name]) + group = response["AutoScalingGroups"][0] + group.should.have.key("MixedInstancesPolicy").equals( + { + "LaunchTemplate": { + "LaunchTemplateSpecification": { + "LaunchTemplateId": lt["LaunchTemplateId"], + "LaunchTemplateName": "launchie", + "Version": "$DEFAULT", + }, + "Overrides": [ + { + "InstanceType": "t2.medium", + "WeightedCapacity": "50", + } + ], + } + } + ) + + @mock_autoscaling def test_set_instance_protection(): mocked_networking = setup_networking() diff --git a/tests/test_autoscaling/test_autoscaling_groups.py b/tests/test_autoscaling/test_autoscaling_groups.py index c101b9493..3be7c5d01 100644 --- a/tests/test_autoscaling/test_autoscaling_groups.py +++ b/tests/test_autoscaling/test_autoscaling_groups.py @@ -44,6 +44,20 @@ class TestAutoScalingGroup(TestCase): group["TerminationPolicies"].should.equal(["Default"]) group["Tags"].should.equal([]) + def test_create_autoscaling_group__additional_params(self): + self.as_client.create_auto_scaling_group( + AutoScalingGroupName="tester_group", + MinSize=1, + MaxSize=2, + LaunchConfigurationName=self.lc_name, + CapacityRebalance=True, + VPCZoneIdentifier=self.mocked_networking["subnet1"], + ) + + group = self.as_client.describe_auto_scaling_groups()["AutoScalingGroups"][0] + group["AutoScalingGroupName"].should.equal("tester_group") + group["CapacityRebalance"].should.equal(True) + def test_list_many_autoscaling_groups(self): for i in range(51):