Add support for Launch Templates in Auto Scaling Groups (#3236)

* Add support for Launch Templates in Auto Scaling Groups

* Use named parameters, simplify parameter validation
This commit is contained in:
Kevin Frommelt 2020-08-26 09:15:07 -05:00 committed by GitHub
parent 2a27e457bf
commit 55b02c6ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 774 additions and 42 deletions

View File

@ -2639,7 +2639,7 @@
- [X] create_internet_gateway - [X] create_internet_gateway
- [X] create_key_pair - [X] create_key_pair
- [X] create_launch_template - [X] create_launch_template
- [ ] create_launch_template_version - [x] create_launch_template_version
- [ ] create_local_gateway_route - [ ] create_local_gateway_route
- [ ] create_local_gateway_route_table_vpc_association - [ ] create_local_gateway_route_table_vpc_association
- [X] create_nat_gateway - [X] create_nat_gateway

View File

@ -21,3 +21,8 @@ class InvalidInstanceError(AutoscalingClientError):
super(InvalidInstanceError, self).__init__( super(InvalidInstanceError, self).__init__(
"ValidationError", "Instance [{0}] is invalid.".format(instance_id) "ValidationError", "Instance [{0}] is invalid.".format(instance_id)
) )
class ValidationError(AutoscalingClientError):
def __init__(self, message):
super(ValidationError, self).__init__("ValidationError", message)

View File

@ -7,6 +7,7 @@ from moto.ec2.exceptions import InvalidInstanceIdError
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel, CloudFormationModel from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import camelcase_to_underscores
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from moto.elb import elb_backends from moto.elb import elb_backends
from moto.elbv2 import elbv2_backends from moto.elbv2 import elbv2_backends
@ -15,6 +16,7 @@ from .exceptions import (
AutoscalingClientError, AutoscalingClientError,
ResourceContentionError, ResourceContentionError,
InvalidInstanceError, InvalidInstanceError,
ValidationError,
) )
# http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown # http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown
@ -233,6 +235,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -242,10 +245,12 @@ class FakeAutoScalingGroup(CloudFormationModel):
placement_group, placement_group,
termination_policies, termination_policies,
autoscaling_backend, autoscaling_backend,
ec2_backend,
tags, tags,
new_instances_protected_from_scale_in=False, new_instances_protected_from_scale_in=False,
): ):
self.autoscaling_backend = autoscaling_backend self.autoscaling_backend = autoscaling_backend
self.ec2_backend = ec2_backend
self.name = name self.name = name
self._set_azs_and_vpcs(availability_zones, vpc_zone_identifier) self._set_azs_and_vpcs(availability_zones, vpc_zone_identifier)
@ -253,10 +258,10 @@ class FakeAutoScalingGroup(CloudFormationModel):
self.max_size = max_size self.max_size = max_size
self.min_size = min_size self.min_size = min_size
self.launch_config = self.autoscaling_backend.launch_configurations[ self.launch_template = None
launch_config_name self.launch_config = None
]
self.launch_config_name = launch_config_name self._set_launch_configuration(launch_config_name, launch_template)
self.default_cooldown = ( self.default_cooldown = (
default_cooldown if default_cooldown else DEFAULT_COOLDOWN default_cooldown if default_cooldown else DEFAULT_COOLDOWN
@ -310,6 +315,34 @@ class FakeAutoScalingGroup(CloudFormationModel):
self.availability_zones = availability_zones self.availability_zones = availability_zones
self.vpc_zone_identifier = vpc_zone_identifier self.vpc_zone_identifier = vpc_zone_identifier
def _set_launch_configuration(self, launch_config_name, launch_template):
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 not (launch_template_id or launch_template_name) or (
launch_template_id and launch_template_name
):
raise ValidationError(
"Valid requests must contain either launchTemplateId or LaunchTemplateName"
)
if launch_template_id:
self.launch_template = self.ec2_backend.get_launch_template(
launch_template_id
)
elif launch_template_name:
self.launch_template = self.ec2_backend.get_launch_template_by_name(
launch_template_name
)
self.launch_template_version = int(launch_template["version"])
@staticmethod @staticmethod
def __set_string_propagate_at_launch_booleans_on_tags(tags): def __set_string_propagate_at_launch_booleans_on_tags(tags):
bool_to_string = {True: "true", False: "false"} bool_to_string = {True: "true", False: "false"}
@ -334,6 +367,10 @@ class FakeAutoScalingGroup(CloudFormationModel):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
launch_config_name = properties.get("LaunchConfigurationName") launch_config_name = properties.get("LaunchConfigurationName")
launch_template = {
camelcase_to_underscores(k): v
for k, v in properties.get("LaunchTemplate", {}).items()
}
load_balancer_names = properties.get("LoadBalancerNames", []) load_balancer_names = properties.get("LoadBalancerNames", [])
target_group_arns = properties.get("TargetGroupARNs", []) target_group_arns = properties.get("TargetGroupARNs", [])
@ -345,6 +382,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
max_size=properties.get("MaxSize"), max_size=properties.get("MaxSize"),
min_size=properties.get("MinSize"), min_size=properties.get("MinSize"),
launch_config_name=launch_config_name, launch_config_name=launch_config_name,
launch_template=launch_template,
vpc_zone_identifier=( vpc_zone_identifier=(
",".join(properties.get("VPCZoneIdentifier", [])) or None ",".join(properties.get("VPCZoneIdentifier", [])) or None
), ),
@ -393,6 +431,38 @@ class FakeAutoScalingGroup(CloudFormationModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.name return self.name
@property
def image_id(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.image_id
return self.launch_config.image_id
@property
def instance_type(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.instance_type
return self.launch_config.instance_type
@property
def user_data(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.user_data
return self.launch_config.user_data
@property
def security_groups(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.security_groups
return self.launch_config.security_groups
def update( def update(
self, self,
availability_zones, availability_zones,
@ -400,6 +470,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -421,11 +492,8 @@ class FakeAutoScalingGroup(CloudFormationModel):
if max_size is not None and max_size < len(self.instance_states): if max_size is not None and max_size < len(self.instance_states):
desired_capacity = max_size desired_capacity = max_size
if launch_config_name: self._set_launch_configuration(launch_config_name, launch_template)
self.launch_config = self.autoscaling_backend.launch_configurations[
launch_config_name
]
self.launch_config_name = launch_config_name
if health_check_period is not None: if health_check_period is not None:
self.health_check_period = health_check_period self.health_check_period = health_check_period
if health_check_type is not None: if health_check_type is not None:
@ -489,12 +557,13 @@ class FakeAutoScalingGroup(CloudFormationModel):
def replace_autoscaling_group_instances(self, count_needed, propagated_tags): def replace_autoscaling_group_instances(self, count_needed, propagated_tags):
propagated_tags[ASG_NAME_TAG] = self.name propagated_tags[ASG_NAME_TAG] = self.name
reservation = self.autoscaling_backend.ec2_backend.add_instances( reservation = self.autoscaling_backend.ec2_backend.add_instances(
self.launch_config.image_id, self.image_id,
count_needed, count_needed,
self.launch_config.user_data, self.user_data,
self.launch_config.security_groups, self.security_groups,
instance_type=self.launch_config.instance_type, instance_type=self.instance_type,
tags={"instance": propagated_tags}, tags={"instance": propagated_tags},
placement=random.choice(self.availability_zones), placement=random.choice(self.availability_zones),
) )
@ -586,6 +655,7 @@ class AutoScalingBackend(BaseBackend):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -609,7 +679,19 @@ class AutoScalingBackend(BaseBackend):
health_check_period = 300 health_check_period = 300
else: else:
health_check_period = make_int(health_check_period) health_check_period = make_int(health_check_period)
if launch_config_name is None and instance_id is not None:
# TODO: Add MixedInstancesPolicy once implemented.
# Verify only a single launch config-like parameter is provided.
params = [launch_config_name, launch_template, instance_id]
num_params = sum([1 for param in params if param])
if num_params != 1:
raise ValidationError(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName, "
"InstanceId or MixedInstancesPolicy parameter."
)
if instance_id:
try: try:
instance = self.ec2_backend.get_instance(instance_id) instance = self.ec2_backend.get_instance(instance_id)
launch_config_name = name launch_config_name = name
@ -626,6 +708,7 @@ class AutoScalingBackend(BaseBackend):
max_size=max_size, max_size=max_size,
min_size=min_size, min_size=min_size,
launch_config_name=launch_config_name, launch_config_name=launch_config_name,
launch_template=launch_template,
vpc_zone_identifier=vpc_zone_identifier, vpc_zone_identifier=vpc_zone_identifier,
default_cooldown=default_cooldown, default_cooldown=default_cooldown,
health_check_period=health_check_period, health_check_period=health_check_period,
@ -635,6 +718,7 @@ class AutoScalingBackend(BaseBackend):
placement_group=placement_group, placement_group=placement_group,
termination_policies=termination_policies, termination_policies=termination_policies,
autoscaling_backend=self, autoscaling_backend=self,
ec2_backend=self.ec2_backend,
tags=tags, tags=tags,
new_instances_protected_from_scale_in=new_instances_protected_from_scale_in, new_instances_protected_from_scale_in=new_instances_protected_from_scale_in,
) )
@ -652,6 +736,7 @@ class AutoScalingBackend(BaseBackend):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -660,19 +745,28 @@ class AutoScalingBackend(BaseBackend):
termination_policies, termination_policies,
new_instances_protected_from_scale_in=None, new_instances_protected_from_scale_in=None,
): ):
# TODO: Add MixedInstancesPolicy once implemented.
# Verify only a single launch config-like parameter is provided.
if launch_config_name and launch_template:
raise ValidationError(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName "
"or MixedInstancesPolicy parameter."
)
group = self.autoscaling_groups[name] group = self.autoscaling_groups[name]
group.update( group.update(
availability_zones, availability_zones=availability_zones,
desired_capacity, desired_capacity=desired_capacity,
max_size, max_size=max_size,
min_size, min_size=min_size,
launch_config_name, launch_config_name=launch_config_name,
vpc_zone_identifier, launch_template=launch_template,
default_cooldown, vpc_zone_identifier=vpc_zone_identifier,
health_check_period, default_cooldown=default_cooldown,
health_check_type, health_check_period=health_check_period,
placement_group, health_check_type=health_check_type,
termination_policies, placement_group=placement_group,
termination_policies=termination_policies,
new_instances_protected_from_scale_in=new_instances_protected_from_scale_in, new_instances_protected_from_scale_in=new_instances_protected_from_scale_in,
) )
return group return group

View File

@ -81,6 +81,7 @@ class AutoScalingResponse(BaseResponse):
min_size=self._get_int_param("MinSize"), min_size=self._get_int_param("MinSize"),
instance_id=self._get_param("InstanceId"), instance_id=self._get_param("InstanceId"),
launch_config_name=self._get_param("LaunchConfigurationName"), launch_config_name=self._get_param("LaunchConfigurationName"),
launch_template=self._get_dict_param("LaunchTemplate."),
vpc_zone_identifier=self._get_param("VPCZoneIdentifier"), vpc_zone_identifier=self._get_param("VPCZoneIdentifier"),
default_cooldown=self._get_int_param("DefaultCooldown"), default_cooldown=self._get_int_param("DefaultCooldown"),
health_check_period=self._get_int_param("HealthCheckGracePeriod"), health_check_period=self._get_int_param("HealthCheckGracePeriod"),
@ -197,6 +198,7 @@ class AutoScalingResponse(BaseResponse):
max_size=self._get_int_param("MaxSize"), max_size=self._get_int_param("MaxSize"),
min_size=self._get_int_param("MinSize"), min_size=self._get_int_param("MinSize"),
launch_config_name=self._get_param("LaunchConfigurationName"), launch_config_name=self._get_param("LaunchConfigurationName"),
launch_template=self._get_dict_param("LaunchTemplate."),
vpc_zone_identifier=self._get_param("VPCZoneIdentifier"), vpc_zone_identifier=self._get_param("VPCZoneIdentifier"),
default_cooldown=self._get_int_param("DefaultCooldown"), default_cooldown=self._get_int_param("DefaultCooldown"),
health_check_period=self._get_int_param("HealthCheckGracePeriod"), health_check_period=self._get_int_param("HealthCheckGracePeriod"),
@ -573,14 +575,31 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
<HealthCheckType>{{ group.health_check_type }}</HealthCheckType> <HealthCheckType>{{ group.health_check_type }}</HealthCheckType>
<CreatedTime>2013-05-06T17:47:15.107Z</CreatedTime> <CreatedTime>2013-05-06T17:47:15.107Z</CreatedTime>
<EnabledMetrics/> <EnabledMetrics/>
{% if group.launch_config_name %}
<LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName> <LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName>
{% elif group.launch_template %}
<LaunchTemplate>
<LaunchTemplateId>{{ group.launch_template.id }}</LaunchTemplateId>
<Version>{{ group.launch_template_version }}</Version>
<LaunchTemplateName>{{ group.launch_template.name }}</LaunchTemplateName>
</LaunchTemplate>
{% endif %}
<Instances> <Instances>
{% for instance_state in group.instance_states %} {% for instance_state in group.instance_states %}
<member> <member>
<HealthStatus>{{ instance_state.health_status }}</HealthStatus> <HealthStatus>{{ instance_state.health_status }}</HealthStatus>
<AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone> <AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone>
<InstanceId>{{ instance_state.instance.id }}</InstanceId> <InstanceId>{{ instance_state.instance.id }}</InstanceId>
<InstanceType>{{ instance_state.instance.instance_type }}</InstanceType>
{% if group.launch_config_name %}
<LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName> <LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName>
{% elif group.launch_template %}
<LaunchTemplate>
<LaunchTemplateId>{{ group.launch_template.id }}</LaunchTemplateId>
<Version>{{ group.launch_template_version }}</Version>
<LaunchTemplateName>{{ group.launch_template.name }}</LaunchTemplateName>
</LaunchTemplate>
{% endif %}
<LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState> <LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState>
<ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn> <ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn>
</member> </member>
@ -666,7 +685,16 @@ DESCRIBE_AUTOSCALING_INSTANCES_TEMPLATE = """<DescribeAutoScalingInstancesRespon
<AutoScalingGroupName>{{ instance_state.instance.autoscaling_group.name }}</AutoScalingGroupName> <AutoScalingGroupName>{{ instance_state.instance.autoscaling_group.name }}</AutoScalingGroupName>
<AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone> <AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone>
<InstanceId>{{ instance_state.instance.id }}</InstanceId> <InstanceId>{{ instance_state.instance.id }}</InstanceId>
<InstanceType>{{ instance_state.instance.instance_type }}</InstanceType>
{% if instance_state.instance.autoscaling_group.launch_config_name %}
<LaunchConfigurationName>{{ instance_state.instance.autoscaling_group.launch_config_name }}</LaunchConfigurationName> <LaunchConfigurationName>{{ instance_state.instance.autoscaling_group.launch_config_name }}</LaunchConfigurationName>
{% elif instance_state.instance.autoscaling_group.launch_template %}
<LaunchTemplate>
<LaunchTemplateId>{{ instance_state.instance.autoscaling_group.launch_template.id }}</LaunchTemplateId>
<Version>{{ instance_state.instance.autoscaling_group.launch_template_version }}</Version>
<LaunchTemplateName>{{ instance_state.instance.autoscaling_group.launch_template.name }}</LaunchTemplateName>
</LaunchTemplate>
{% endif %}
<LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState> <LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState>
<ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn> <ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn>
</member> </member>

View File

@ -538,8 +538,8 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
returns returns
{ {
"SlaveInstanceType": "m1.small", "slave_instance_type": "m1.small",
"InstanceCount": "1", "instance_count": "1",
} }
""" """
params = {} params = {}

View File

@ -5386,6 +5386,22 @@ class LaunchTemplateVersion(object):
self.description = description self.description = description
self.create_time = utc_date_and_time() self.create_time = utc_date_and_time()
@property
def image_id(self):
return self.data.get("ImageId", "")
@property
def instance_type(self):
return self.data.get("InstanceType", "")
@property
def security_groups(self):
return self.data.get("SecurityGroups", [])
@property
def user_data(self):
return self.data.get("UserData", "")
class LaunchTemplate(TaggedEC2Resource): class LaunchTemplate(TaggedEC2Resource):
def __init__(self, backend, name, template_data, version_description): def __init__(self, backend, name, template_data, version_description):

View File

@ -17,6 +17,7 @@ from moto import (
mock_elb, mock_elb,
mock_autoscaling_deprecated, mock_autoscaling_deprecated,
mock_ec2, mock_ec2,
mock_cloudformation,
) )
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
@ -164,7 +165,7 @@ def test_list_many_autoscaling_groups():
@mock_autoscaling @mock_autoscaling
@mock_ec2 @mock_ec2
def test_list_many_autoscaling_groups(): def test_propogate_tags():
mocked_networking = setup_networking() mocked_networking = setup_networking()
conn = boto3.client("autoscaling", region_name="us-east-1") conn = boto3.client("autoscaling", region_name="us-east-1")
conn.create_launch_configuration(LaunchConfigurationName="TestLC") conn.create_launch_configuration(LaunchConfigurationName="TestLC")
@ -692,7 +693,7 @@ def test_detach_load_balancer():
def test_create_autoscaling_group_boto3(): def test_create_autoscaling_group_boto3():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration"
) )
response = client.create_auto_scaling_group( response = client.create_auto_scaling_group(
@ -798,13 +799,171 @@ def test_create_autoscaling_group_from_invalid_instance_id():
@mock_autoscaling @mock_autoscaling
def test_describe_autoscaling_groups_boto3(): @mock_ec2
def test_create_autoscaling_group_from_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
response = client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={
"LaunchTemplateId": template["LaunchTemplateId"],
"Version": str(template["LatestVersionNumber"]),
},
MinSize=1,
MaxSize=3,
DesiredCapacity=2,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_autoscaling
@mock_ec2
def test_create_autoscaling_group_no_template_ref():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"Version": str(template["LatestVersionNumber"])},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either launchTemplateId or LaunchTemplateName"
)
@mock_autoscaling
@mock_ec2
def test_create_autoscaling_group_multiple_template_ref():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": template["LaunchTemplateName"],
"Version": str(template["LatestVersionNumber"]),
},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either launchTemplateId or LaunchTemplateName"
)
@mock_autoscaling
def test_create_autoscaling_group_boto3_no_launch_configuration():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName, "
"InstanceId or MixedInstancesPolicy parameter."
)
@mock_autoscaling
@mock_ec2
def test_create_autoscaling_group_boto3_multiple_launch_configurations():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration"
) )
_ = client.create_auto_scaling_group(
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration",
LaunchTemplate={
"LaunchTemplateId": template["LaunchTemplateId"],
"Version": str(template["LatestVersionNumber"]),
},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName, "
"InstanceId or MixedInstancesPolicy parameter."
)
@mock_autoscaling
def test_describe_autoscaling_groups_boto3_launch_config():
mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration", InstanceType="t2.micro",
)
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration", LaunchConfigurationName="test_launch_configuration",
MinSize=0, MinSize=0,
@ -818,22 +977,72 @@ def test_describe_autoscaling_groups_boto3():
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
group = response["AutoScalingGroups"][0] group = response["AutoScalingGroups"][0]
group["AutoScalingGroupName"].should.equal("test_asg") group["AutoScalingGroupName"].should.equal("test_asg")
group["LaunchConfigurationName"].should.equal("test_launch_configuration")
group.should_not.have.key("LaunchTemplate")
group["AvailabilityZones"].should.equal(["us-east-1a"]) group["AvailabilityZones"].should.equal(["us-east-1a"])
group["VPCZoneIdentifier"].should.equal(mocked_networking["subnet1"]) group["VPCZoneIdentifier"].should.equal(mocked_networking["subnet1"])
group["NewInstancesProtectedFromScaleIn"].should.equal(True) group["NewInstancesProtectedFromScaleIn"].should.equal(True)
for instance in group["Instances"]: for instance in group["Instances"]:
instance["LaunchConfigurationName"].should.equal("test_launch_configuration")
instance.should_not.have.key("LaunchTemplate")
instance["AvailabilityZone"].should.equal("us-east-1a") instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True) instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling @mock_autoscaling
def test_describe_autoscaling_instances_boto3(): @mock_ec2
def test_describe_autoscaling_groups_boto3_launch_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"LaunchTemplateName": "test_launch_template", "Version": "1"},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=True,
)
expected_launch_template = {
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": "test_launch_template",
"Version": "1",
}
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
group = response["AutoScalingGroups"][0]
group["AutoScalingGroupName"].should.equal("test_asg")
group["LaunchTemplate"].should.equal(expected_launch_template)
group.should_not.have.key("LaunchConfigurationName")
group["AvailabilityZones"].should.equal(["us-east-1a"])
group["VPCZoneIdentifier"].should.equal(mocked_networking["subnet1"])
group["NewInstancesProtectedFromScaleIn"].should.equal(True)
for instance in group["Instances"]:
instance["LaunchTemplate"].should.equal(expected_launch_template)
instance.should_not.have.key("LaunchConfigurationName")
instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling
def test_describe_autoscaling_instances_boto3_launch_config():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration", InstanceType="t2.micro",
) )
_ = client.create_auto_scaling_group( client.create_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration", LaunchConfigurationName="test_launch_configuration",
MinSize=0, MinSize=0,
@ -846,9 +1055,51 @@ def test_describe_autoscaling_instances_boto3():
response = client.describe_auto_scaling_instances() response = client.describe_auto_scaling_instances()
len(response["AutoScalingInstances"]).should.equal(5) len(response["AutoScalingInstances"]).should.equal(5)
for instance in response["AutoScalingInstances"]: for instance in response["AutoScalingInstances"]:
instance["LaunchConfigurationName"].should.equal("test_launch_configuration")
instance.should_not.have.key("LaunchTemplate")
instance["AutoScalingGroupName"].should.equal("test_asg") instance["AutoScalingGroupName"].should.equal("test_asg")
instance["AvailabilityZone"].should.equal("us-east-1a") instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True) instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling
@mock_ec2
def test_describe_autoscaling_instances_boto3_launch_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"LaunchTemplateName": "test_launch_template", "Version": "1"},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=True,
)
expected_launch_template = {
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": "test_launch_template",
"Version": "1",
}
response = client.describe_auto_scaling_instances()
len(response["AutoScalingInstances"]).should.equal(5)
for instance in response["AutoScalingInstances"]:
instance["LaunchTemplate"].should.equal(expected_launch_template)
instance.should_not.have.key("LaunchConfigurationName")
instance["AutoScalingGroupName"].should.equal("test_asg")
instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling @mock_autoscaling
@ -885,13 +1136,16 @@ def test_describe_autoscaling_instances_instanceid_filter():
@mock_autoscaling @mock_autoscaling
def test_update_autoscaling_group_boto3(): def test_update_autoscaling_group_boto3_launch_config():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration"
) )
_ = client.create_auto_scaling_group( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration_new"
)
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration", LaunchConfigurationName="test_launch_configuration",
MinSize=0, MinSize=0,
@ -901,8 +1155,9 @@ def test_update_autoscaling_group_boto3():
NewInstancesProtectedFromScaleIn=True, NewInstancesProtectedFromScaleIn=True,
) )
_ = client.update_auto_scaling_group( client.update_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration_new",
MinSize=1, MinSize=1,
VPCZoneIdentifier="{subnet1},{subnet2}".format( VPCZoneIdentifier="{subnet1},{subnet2}".format(
subnet1=mocked_networking["subnet1"], subnet2=mocked_networking["subnet2"] subnet1=mocked_networking["subnet1"], subnet2=mocked_networking["subnet2"]
@ -912,6 +1167,64 @@ def test_update_autoscaling_group_boto3():
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"]) response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
group = response["AutoScalingGroups"][0] group = response["AutoScalingGroups"][0]
group["LaunchConfigurationName"].should.equal("test_launch_configuration_new")
group["MinSize"].should.equal(1)
set(group["AvailabilityZones"]).should.equal({"us-east-1a", "us-east-1b"})
group["NewInstancesProtectedFromScaleIn"].should.equal(False)
@mock_autoscaling
@mock_ec2
def test_update_autoscaling_group_boto3_launch_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template_new",
LaunchTemplateData={
"ImageId": "ami-1ea5b10a3d8867db4",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"LaunchTemplateName": "test_launch_template", "Version": "1"},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=True,
)
client.update_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={
"LaunchTemplateName": "test_launch_template_new",
"Version": "1",
},
MinSize=1,
VPCZoneIdentifier="{subnet1},{subnet2}".format(
subnet1=mocked_networking["subnet1"], subnet2=mocked_networking["subnet2"]
),
NewInstancesProtectedFromScaleIn=False,
)
expected_launch_template = {
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": "test_launch_template_new",
"Version": "1",
}
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
group = response["AutoScalingGroups"][0]
group["LaunchTemplate"].should.equal(expected_launch_template)
group["MinSize"].should.equal(1) group["MinSize"].should.equal(1)
set(group["AvailabilityZones"]).should.equal({"us-east-1a", "us-east-1b"}) set(group["AvailabilityZones"]).should.equal({"us-east-1a", "us-east-1b"})
group["NewInstancesProtectedFromScaleIn"].should.equal(False) group["NewInstancesProtectedFromScaleIn"].should.equal(False)
@ -966,7 +1279,7 @@ def test_update_autoscaling_group_max_size_desired_capacity_change():
@mock_autoscaling @mock_autoscaling
def test_autoscaling_taqs_update_boto3(): def test_autoscaling_tags_update_boto3():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( _ = client.create_launch_configuration(

View File

@ -0,0 +1,276 @@
import boto3
import sure # noqa
from moto import (
mock_autoscaling,
mock_cloudformation,
mock_ec2,
)
from utils import setup_networking
@mock_autoscaling
@mock_cloudformation
def test_launch_configuration():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
client = boto3.client("autoscaling", region_name="us-east-1")
stack_name = "test-launch-configuration"
cf_template = """
Resources:
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: ami-0cc293023f983ed53
InstanceType: t2.micro
LaunchConfigurationName: test_launch_configuration
Outputs:
LaunchConfigurationName:
Value: !Ref LaunchConfiguration
""".strip()
cf_client.create_stack(
StackName=stack_name, TemplateBody=cf_template,
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_launch_configuration")
lc = client.describe_launch_configurations()["LaunchConfigurations"][0]
lc["LaunchConfigurationName"].should.be.equal("test_launch_configuration")
lc["ImageId"].should.be.equal("ami-0cc293023f983ed53")
lc["InstanceType"].should.be.equal("t2.micro")
cf_template = """
Resources:
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: ami-1ea5b10a3d8867db4
InstanceType: m5.large
LaunchConfigurationName: test_launch_configuration
Outputs:
LaunchConfigurationName:
Value: !Ref LaunchConfiguration
""".strip()
cf_client.update_stack(
StackName=stack_name, TemplateBody=cf_template,
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_launch_configuration")
lc = client.describe_launch_configurations()["LaunchConfigurations"][0]
lc["LaunchConfigurationName"].should.be.equal("test_launch_configuration")
lc["ImageId"].should.be.equal("ami-1ea5b10a3d8867db4")
lc["InstanceType"].should.be.equal("m5.large")
@mock_autoscaling
@mock_cloudformation
def test_autoscaling_group_from_launch_config():
subnet_id = setup_networking()["subnet1"]
cf_client = boto3.client("cloudformation", region_name="us-east-1")
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration", InstanceType="t2.micro",
)
stack_name = "test-auto-scaling-group"
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchConfigurationName: test_launch_configuration
MaxSize: "5"
MinSize: "1"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.create_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[{"ParameterKey": "SubnetId", "ParameterValue": subnet_id}],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(1)
asg["MaxSize"].should.be.equal(5)
asg["LaunchConfigurationName"].should.be.equal("test_launch_configuration")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration_new",
InstanceType="t2.micro",
)
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchConfigurationName: test_launch_configuration_new
MaxSize: "6"
MinSize: "2"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.update_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[{"ParameterKey": "SubnetId", "ParameterValue": subnet_id}],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(2)
asg["MaxSize"].should.be.equal(6)
asg["LaunchConfigurationName"].should.be.equal("test_launch_configuration_new")
@mock_autoscaling
@mock_cloudformation
@mock_ec2
def test_autoscaling_group_from_launch_template():
subnet_id = setup_networking()["subnet1"]
cf_client = boto3.client("cloudformation", region_name="us-east-1")
ec2_client = boto3.client("ec2", region_name="us-east-1")
client = boto3.client("autoscaling", region_name="us-east-1")
template_response = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)
launch_template_id = template_response["LaunchTemplate"]["LaunchTemplateId"]
stack_name = "test-auto-scaling-group"
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
LaunchTemplateId:
Type: String
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplateId
Version: "1"
MaxSize: "5"
MinSize: "1"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.create_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[
{"ParameterKey": "SubnetId", "ParameterValue": subnet_id},
{"ParameterKey": "LaunchTemplateId", "ParameterValue": launch_template_id},
],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(1)
asg["MaxSize"].should.be.equal(5)
lt = asg["LaunchTemplate"]
lt["LaunchTemplateId"].should.be.equal(launch_template_id)
lt["LaunchTemplateName"].should.be.equal("test_launch_template")
lt["Version"].should.be.equal("1")
template_response = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template_new",
LaunchTemplateData={
"ImageId": "ami-1ea5b10a3d8867db4",
"InstanceType": "m5.large",
},
)
launch_template_id = template_response["LaunchTemplate"]["LaunchTemplateId"]
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
LaunchTemplateId:
Type: String
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplateId
Version: "1"
MaxSize: "6"
MinSize: "2"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.update_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[
{"ParameterKey": "SubnetId", "ParameterValue": subnet_id},
{"ParameterKey": "LaunchTemplateId", "ParameterValue": launch_template_id},
],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(2)
asg["MaxSize"].should.be.equal(6)
lt = asg["LaunchTemplate"]
lt["LaunchTemplateId"].should.be.equal(launch_template_id)
lt["LaunchTemplateName"].should.be.equal("test_launch_template_new")
lt["Version"].should.be.equal("1")