Repository: getmoto/moto

Issue:
CreateAutoScalingGroup with MixedInstancesPolicy not working
When using moto as backend
```
aws  --endpoint=http://127.0.0.1:5000 autoscaling create-auto-scaling-group --auto-scaling-group-name group2 \
  --min-size 1 --max-size 10  --availability-zones us-east-1a \
  --mixed-instances-policy 'LaunchTemplate={LaunchTemplateSpecification={LaunchTemplateName=tmpl2},Overrides={InstanceType=t3.micro}}'
```

Gives:
```
An error occurred (ValidationError) when calling the CreateAutoScalingGroup operation: Valid requests must contain either LaunchTemplate, LaunchConfigurationName, InstanceId or MixedInstancesPolicy parameter.
```

On real AWS:
```
aws autoscaling create-auto-scaling-group --auto-scaling-group-name group2 \
  --min-size 1 --max-size 10  --availability-zones us-east-1a \
  --mixed-instances-policy 'LaunchTemplate={LaunchTemplateSpecification={LaunchTemplateName=tmpl2},Overrides={InstanceType=t3.micro}}'
```
works fine


Discussion:
https://github.com/spulec/moto/blob/ab045f5bebff4de84ac33053ef764efa32405829/moto/autoscaling/models.py#L888

Going to mark this as an enhancement instead, as we just need to add support for that parameter.

Patch:
diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py
--- a/moto/autoscaling/models.py
+++ b/moto/autoscaling/models.py
@@ -368,6 +368,8 @@ def __init__(
         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 @@ def __init__(
         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 @@ def __init__(
         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 @@ def __init__(
         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 @@ def _set_azs_and_vpcs(self, availability_zones, vpc_zone_identifier, update=Fals
         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 @@ def _set_launch_configuration(self, launch_config_name, launch_template):
                 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 @@ def update(
             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 @@ def create_auto_scaling_group(
         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 @@ def create_auto_scaling_group(
         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 @@ def create_auto_scaling_group(
             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
--- a/moto/autoscaling/responses.py
+++ b/moto/autoscaling/responses.py
@@ -80,6 +80,7 @@ def delete_launch_configuration(self):
         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 @@ def create_auto_scaling_group(self):
             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 @@ def create_auto_scaling_group(self):
             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 @@ def enable_metrics_collection(self):
         <CreatedTime>2013-05-06T17:47:15.107Z</CreatedTime>
         {% if group.launch_config_name %}
         <LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName>
+        {% elif group.mixed_instance_policy %}
+        <MixedInstancesPolicy>
+          <LaunchTemplate>
+            <LaunchTemplateSpecification>
+              <LaunchTemplateId>{{ group.launch_template.id }}</LaunchTemplateId>
+              <Version>{{ group.launch_template_version }}</Version>
+              <LaunchTemplateName>{{ group.launch_template.name }}</LaunchTemplateName>
+            </LaunchTemplateSpecification>
+            {% if group.mixed_instance_policy.get("LaunchTemplate", {}).get("Overrides", []) %}
+            <Overrides>
+              {% for member in group.mixed_instance_policy.get("LaunchTemplate", {}).get("Overrides", []) %}
+              <member>
+                {% if member.get("InstanceType") %}
+                <InstanceType>{{ member.get("InstanceType") }}</InstanceType>
+                {% endif %}
+                {% if member.get("WeightedCapacity") %}
+                <WeightedCapacity>{{ member.get("WeightedCapacity") }}</WeightedCapacity>
+                {% endif %}
+              </member>
+              {% endfor %}
+            </Overrides>
+            {% endif %}
+          </LaunchTemplate>
+        </MixedInstancesPolicy>
         {% elif group.launch_template %}
         <LaunchTemplate>
           <LaunchTemplateId>{{ group.launch_template.id }}</LaunchTemplateId>
@@ -777,6 +804,7 @@ def enable_metrics_collection(self):
           {% endfor %}
         </Instances>
         <DesiredCapacity>{{ group.desired_capacity }}</DesiredCapacity>
+        <CapacityRebalance>{{ 'true' if group.capacity_rebalance else 'false' }}</CapacityRebalance>
         <AvailabilityZones>
           {% for availability_zone in group.availability_zones %}
           <member>{{ availability_zone }}</member>
@@ -833,6 +861,7 @@ def enable_metrics_collection(self):
           {% endfor %}
         </EnabledMetrics>
         {% endif %}
+        <ServiceLinkedRoleARN>{{ group.service_linked_role }}</ServiceLinkedRoleARN>
       </member>
       {% endfor %}
     </AutoScalingGroups>


Test patch:
diff --git a/tests/terraformtests/terraform-tests.success.txt b/tests/terraformtests/terraform-tests.success.txt
--- 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
--- 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
--- a/tests/test_autoscaling/test_autoscaling_groups.py
+++ b/tests/test_autoscaling/test_autoscaling_groups.py
@@ -44,6 +44,20 @@ def test_create_autoscaling_groups_defaults(self):
         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):