diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index fd8efd54f..d082daa25 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -4,6 +4,7 @@ from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.ec2 import ec2_backends from moto.elb import elb_backends +from moto.elbv2 import elbv2_backends from moto.elb.exceptions import LoadBalancerNotFoundError from .exceptions import ( ResourceContentionError, @@ -149,7 +150,7 @@ class FakeAutoScalingGroup(BaseModel): def __init__(self, name, availability_zones, desired_capacity, max_size, min_size, launch_config_name, vpc_zone_identifier, default_cooldown, health_check_period, health_check_type, - load_balancers, placement_group, termination_policies, + load_balancers, target_group_arns, placement_group, termination_policies, autoscaling_backend, tags): self.autoscaling_backend = autoscaling_backend self.name = name @@ -166,6 +167,7 @@ class FakeAutoScalingGroup(BaseModel): self.health_check_period = health_check_period self.health_check_type = health_check_type if health_check_type else "EC2" self.load_balancers = load_balancers + self.target_group_arns = target_group_arns self.placement_group = placement_group self.termination_policies = termination_policies @@ -179,6 +181,7 @@ class FakeAutoScalingGroup(BaseModel): launch_config_name = properties.get("LaunchConfigurationName") load_balancer_names = properties.get("LoadBalancerNames", []) + target_group_arns = properties.get("TargetGroupARNs", []) backend = autoscaling_backends[region_name] group = backend.create_autoscaling_group( @@ -194,6 +197,7 @@ class FakeAutoScalingGroup(BaseModel): health_check_period=properties.get("HealthCheckGracePeriod"), health_check_type=properties.get("HealthCheckType"), load_balancers=load_balancer_names, + target_group_arns=target_group_arns, placement_group=None, termination_policies=properties.get("TerminationPolicies", []), tags=properties.get("Tags", []), @@ -299,20 +303,26 @@ class FakeAutoScalingGroup(BaseModel): instance.autoscaling_group = self self.instance_states.append(InstanceState(instance)) + def append_target_groups(self, target_group_arns): + append = [x for x in target_group_arns if x not in self.target_group_arns] + self.target_group_arns.extend(append) + class AutoScalingBackend(BaseBackend): - def __init__(self, ec2_backend, elb_backend): + def __init__(self, ec2_backend, elb_backend, elbv2_backend): self.autoscaling_groups = OrderedDict() self.launch_configurations = OrderedDict() self.policies = {} self.ec2_backend = ec2_backend self.elb_backend = elb_backend + self.elbv2_backend = elbv2_backend def reset(self): ec2_backend = self.ec2_backend elb_backend = self.elb_backend + elbv2_backend = self.elbv2_backend self.__dict__ = {} - self.__init__(ec2_backend, elb_backend) + self.__init__(ec2_backend, elb_backend, elbv2_backend) def create_launch_configuration(self, name, image_id, key_name, kernel_id, ramdisk_id, security_groups, user_data, instance_type, @@ -352,7 +362,8 @@ class AutoScalingBackend(BaseBackend): launch_config_name, vpc_zone_identifier, default_cooldown, health_check_period, health_check_type, load_balancers, - placement_group, termination_policies, tags): + target_group_arns, placement_group, + termination_policies, tags): def make_int(value): return int(value) if value is not None else value @@ -378,6 +389,7 @@ class AutoScalingBackend(BaseBackend): health_check_period=health_check_period, health_check_type=health_check_type, load_balancers=load_balancers, + target_group_arns=target_group_arns, placement_group=placement_group, termination_policies=termination_policies, autoscaling_backend=self, @@ -386,6 +398,7 @@ class AutoScalingBackend(BaseBackend): self.autoscaling_groups[name] = group self.update_attached_elbs(group.name) + self.update_attached_target_groups(group.name) return group def update_autoscaling_group(self, name, availability_zones, @@ -522,8 +535,25 @@ class AutoScalingBackend(BaseBackend): self.elb_backend.deregister_instances( elb.name, elb_instace_ids - group_instance_ids) - def create_or_update_tags(self, tags): + def update_attached_target_groups(self, group_name): + group = self.autoscaling_groups[group_name] + group_instance_ids = set( + state.instance.id for state in group.instance_states) + # no action necessary if target_group_arns is empty + if not group.target_group_arns: + return + + target_groups = self.elbv2_backend.describe_target_groups( + target_group_arns=group.target_group_arns, + load_balancer_arn=None, + names=None) + + for target_group in target_groups: + asg_targets = [{'id': x, 'port': target_group.port} for x in group_instance_ids] + self.elbv2_backend.register_targets(target_group.arn, (asg_targets)) + + def create_or_update_tags(self, tags): for tag in tags: group_name = tag["resource_id"] group = self.autoscaling_groups[group_name] @@ -562,8 +592,23 @@ class AutoScalingBackend(BaseBackend): elb.name, group_instance_ids) group.load_balancers = [x for x in group.load_balancers if x not in load_balancer_names] + def attach_load_balancer_target_groups(self, group_name, target_group_arns): + group = self.autoscaling_groups[group_name] + group.append_target_groups(target_group_arns) + self.update_attached_target_groups(group_name) + + def describe_load_balancer_target_groups(self, group_name): + return self.autoscaling_groups[group_name].target_group_arns + + def detach_load_balancer_target_groups(self, group_name, target_group_arns): + group = self.autoscaling_groups[group_name] + group.target_group_arns = [x for x in group.target_group_arns if x not in target_group_arns] + for target_group in target_group_arns: + asg_targets = [{'id': x.instance.id} for x in group.instance_states] + self.elbv2_backend.deregister_targets(target_group, (asg_targets)) + autoscaling_backends = {} for region, ec2_backend in ec2_backends.items(): autoscaling_backends[region] = AutoScalingBackend( - ec2_backend, elb_backends[region]) + ec2_backend, elb_backends[region], elbv2_backends[region]) diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py index 832103775..c44df3357 100644 --- a/moto/autoscaling/responses.py +++ b/moto/autoscaling/responses.py @@ -80,6 +80,7 @@ class AutoScalingResponse(BaseResponse): health_check_period=self._get_int_param('HealthCheckGracePeriod'), health_check_type=self._get_param('HealthCheckType'), load_balancers=self._get_multi_param('LoadBalancerNames.member'), + target_group_arns=self._get_multi_param('TargetGroupARNs.member'), placement_group=self._get_param('PlacementGroup'), termination_policies=self._get_multi_param( 'TerminationPolicies.member'), @@ -92,7 +93,7 @@ class AutoScalingResponse(BaseResponse): @amzn_request_id def attach_instances(self): group_name = self._get_param('AutoScalingGroupName') - instance_ids = self._get_multi_param("InstanceIds.member") + instance_ids = self._get_multi_param('InstanceIds.member') self.autoscaling_backend.attach_instances( group_name, instance_ids) template = self.response_template(ATTACH_INSTANCES_TEMPLATE) @@ -102,7 +103,7 @@ class AutoScalingResponse(BaseResponse): @amzn_request_id def detach_instances(self): group_name = self._get_param('AutoScalingGroupName') - instance_ids = self._get_multi_param("InstanceIds.member") + instance_ids = self._get_multi_param('InstanceIds.member') should_decrement_string = self._get_param('ShouldDecrementDesiredCapacity') if should_decrement_string == 'true': should_decrement = True @@ -113,6 +114,37 @@ class AutoScalingResponse(BaseResponse): template = self.response_template(DETACH_INSTANCES_TEMPLATE) return template.render(detached_instances=detached_instances) + @amz_crc32 + @amzn_request_id + def attach_load_balancer_target_groups(self): + group_name = self._get_param('AutoScalingGroupName') + target_group_arns = self._get_multi_param('TargetGroupARNs.member') + + self.autoscaling_backend.attach_load_balancer_target_groups( + group_name, target_group_arns) + template = self.response_template(ATTACH_LOAD_BALANCER_TARGET_GROUPS_TEMPLATE) + return template.render() + + @amz_crc32 + @amzn_request_id + def describe_load_balancer_target_groups(self): + group_name = self._get_param('AutoScalingGroupName') + target_group_arns = self.autoscaling_backend.describe_load_balancer_target_groups( + group_name) + template = self.response_template(DESCRIBE_LOAD_BALANCER_TARGET_GROUPS) + return template.render(target_group_arns=target_group_arns) + + @amz_crc32 + @amzn_request_id + def detach_load_balancer_target_groups(self): + group_name = self._get_param('AutoScalingGroupName') + target_group_arns = self._get_multi_param('TargetGroupARNs.member') + + self.autoscaling_backend.detach_load_balancer_target_groups( + group_name, target_group_arns) + template = self.response_template(DETACH_LOAD_BALANCER_TARGET_GROUPS_TEMPLATE) + return template.render() + def describe_auto_scaling_groups(self): names = self._get_multi_param("AutoScalingGroupNames.member") token = self._get_param("NextToken") @@ -338,6 +370,14 @@ CREATE_AUTOSCALING_GROUP_TEMPLATE = """ + + + +{{ requestid }} + +""" + ATTACH_INSTANCES_TEMPLATE = """ @@ -346,6 +386,22 @@ ATTACH_INSTANCES_TEMPLATE = """ + + + {% for arn in target_group_arns %} + + {{ arn }} + Added + + {% endfor %} + + + +{{ requestid }} + +""" + DETACH_INSTANCES_TEMPLATE = """ @@ -372,6 +428,14 @@ DETACH_INSTANCES_TEMPLATE = """ + + + +{{ requestid }} + +""" + DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """ diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py index b0bbc88a8..48074d5a7 100644 --- a/tests/test_autoscaling/test_autoscaling.py +++ b/tests/test_autoscaling/test_autoscaling.py @@ -599,6 +599,11 @@ def test_attach_load_balancer(): ) list(response['LoadBalancerDescriptions'][0]['Instances']).should.have.length_of(INSTANCE_COUNT) + response = client.describe_auto_scaling_groups( + AutoScalingGroupNames=["test_asg"] + ) + list(response['AutoScalingGroups'][0]['LoadBalancerNames']).should.have.length_of(1) + @mock_autoscaling @mock_elb diff --git a/tests/test_autoscaling/test_elbv2.py b/tests/test_autoscaling/test_elbv2.py new file mode 100644 index 000000000..89ec4a399 --- /dev/null +++ b/tests/test_autoscaling/test_elbv2.py @@ -0,0 +1,131 @@ +from __future__ import unicode_literals +import boto3 + +from moto import mock_autoscaling, mock_ec2, mock_elbv2 + +@mock_elbv2 +@mock_ec2 +@mock_autoscaling +def test_attach_detach_target_groups(): + INSTANCE_COUNT = 2 + client = boto3.client('autoscaling', region_name='us-east-1') + elbv2_client = boto3.client('elbv2', region_name='us-east-1') + ec2 = boto3.resource('ec2', region_name='us-east-1') + + vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') + + response = elbv2_client.create_target_group( + Name='a-target', + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + target_group_arn = response['TargetGroups'][0]['TargetGroupArn'] + + client.create_launch_configuration( + LaunchConfigurationName='test_launch_configuration') + + # create asg, attach to target group on create + client.create_auto_scaling_group( + AutoScalingGroupName='test_asg', + LaunchConfigurationName='test_launch_configuration', + MinSize=0, + MaxSize=INSTANCE_COUNT, + DesiredCapacity=INSTANCE_COUNT, + TargetGroupARNs=[target_group_arn], + VPCZoneIdentifier=vpc.id) + # create asg without attaching to target group + client.create_auto_scaling_group( + AutoScalingGroupName='test_asg2', + LaunchConfigurationName='test_launch_configuration', + MinSize=0, + MaxSize=INSTANCE_COUNT, + DesiredCapacity=INSTANCE_COUNT, + VPCZoneIdentifier=vpc.id) + + response = client.describe_load_balancer_target_groups( + AutoScalingGroupName='test_asg') + list(response['LoadBalancerTargetGroups']).should.have.length_of(1) + + response = elbv2_client.describe_target_health( + TargetGroupArn=target_group_arn) + list(response['TargetHealthDescriptions']).should.have.length_of(INSTANCE_COUNT) + + client.attach_load_balancer_target_groups( + AutoScalingGroupName='test_asg2', + TargetGroupARNs=[target_group_arn]) + + response = elbv2_client.describe_target_health( + TargetGroupArn=target_group_arn) + list(response['TargetHealthDescriptions']).should.have.length_of(INSTANCE_COUNT * 2) + + response = client.detach_load_balancer_target_groups( + AutoScalingGroupName='test_asg2', + TargetGroupARNs=[target_group_arn]) + response = elbv2_client.describe_target_health( + TargetGroupArn=target_group_arn) + list(response['TargetHealthDescriptions']).should.have.length_of(INSTANCE_COUNT) + +@mock_elbv2 +@mock_ec2 +@mock_autoscaling +def test_detach_all_target_groups(): + INSTANCE_COUNT = 2 + client = boto3.client('autoscaling', region_name='us-east-1') + elbv2_client = boto3.client('elbv2', region_name='us-east-1') + ec2 = boto3.resource('ec2', region_name='us-east-1') + + vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') + + response = elbv2_client.create_target_group( + Name='a-target', + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + target_group_arn = response['TargetGroups'][0]['TargetGroupArn'] + + client.create_launch_configuration( + LaunchConfigurationName='test_launch_configuration') + + client.create_auto_scaling_group( + AutoScalingGroupName='test_asg', + LaunchConfigurationName='test_launch_configuration', + MinSize=0, + MaxSize=INSTANCE_COUNT, + DesiredCapacity=INSTANCE_COUNT, + TargetGroupARNs=[target_group_arn], + VPCZoneIdentifier=vpc.id) + + response = client.describe_load_balancer_target_groups( + AutoScalingGroupName='test_asg') + list(response['LoadBalancerTargetGroups']).should.have.length_of(1) + + response = elbv2_client.describe_target_health( + TargetGroupArn=target_group_arn) + list(response['TargetHealthDescriptions']).should.have.length_of(INSTANCE_COUNT) + + response = client.detach_load_balancer_target_groups( + AutoScalingGroupName='test_asg', + TargetGroupARNs=[target_group_arn]) + + response = elbv2_client.describe_target_health( + TargetGroupArn=target_group_arn) + list(response['TargetHealthDescriptions']).should.have.length_of(0) + response = client.describe_load_balancer_target_groups( + AutoScalingGroupName='test_asg') + list(response['LoadBalancerTargetGroups']).should.have.length_of(0)