From c5ce2848befef6b8475da22d6ab3804493e19f6a Mon Sep 17 00:00:00 2001 From: William Richard Date: Wed, 21 Jun 2017 12:58:01 -0400 Subject: [PATCH] Boto3 and cloudformation have different keys for auto scaling tags - handle that gracefully --- moto/autoscaling/models.py | 19 +-- tests/test_autoscaling/test_autoscaling.py | 33 +++-- .../test_cloudformation_stack_integration.py | 119 ++++++++++++------ 3 files changed, 115 insertions(+), 56 deletions(-) diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index a2fcb2a63..9df9fea12 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -13,14 +13,12 @@ ASG_NAME_TAG = "aws:autoscaling:groupName" class InstanceState(object): - def __init__(self, instance, lifecycle_state="InService"): self.instance = instance self.lifecycle_state = lifecycle_state class FakeScalingPolicy(BaseModel): - def __init__(self, name, policy_type, adjustment_type, as_name, scaling_adjustment, cooldown, autoscaling_backend): self.name = name @@ -47,7 +45,6 @@ class FakeScalingPolicy(BaseModel): class FakeLaunchConfiguration(BaseModel): - def __init__(self, name, image_id, key_name, ramdisk_id, kernel_id, security_groups, user_data, instance_type, instance_monitoring, instance_profile_name, spot_price, ebs_optimized, associate_public_ip_address, block_device_mapping_dict): @@ -146,7 +143,6 @@ class FakeLaunchConfiguration(BaseModel): 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, @@ -261,11 +257,17 @@ class FakeAutoScalingGroup(BaseModel): if self.desired_capacity > curr_instance_count: # Need more instances - count_needed = int(self.desired_capacity) - \ - int(curr_instance_count) + count_needed = int(self.desired_capacity) - int(curr_instance_count) + + propagated_tags = {} + for tag in self.tags: + # boto uses 'propagate_at_launch + # boto3 and cloudformation use PropagateAtLaunch + if 'propagate_at_launch' in tag and tag['propagate_at_launch'] == 'true': + propagated_tags[tag['key']] = tag['value'] + if 'PropagateAtLaunch' in tag and tag['PropagateAtLaunch']: + propagated_tags[tag['Key']] = tag['Value'] - propagated_tags = {t['key']: t['value'] for t in self.tags - if t['propagate_at_launch'] == 'true'} propagated_tags[ASG_NAME_TAG] = self.name reservation = self.autoscaling_backend.ec2_backend.add_instances( self.launch_config.image_id, @@ -290,7 +292,6 @@ class FakeAutoScalingGroup(BaseModel): class AutoScalingBackend(BaseBackend): - def __init__(self, ec2_backend, elb_backend): self.autoscaling_groups = OrderedDict() self.launch_configurations = OrderedDict() diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py index 5cc697785..b919eb71c 100644 --- a/tests/test_autoscaling/test_autoscaling.py +++ b/tests/test_autoscaling/test_autoscaling.py @@ -138,6 +138,7 @@ def test_list_many_autoscaling_groups(): groups.should.have.length_of(51) assert 'NextToken' not in response2.keys() + @mock_autoscaling @mock_ec2 def test_list_many_autoscaling_groups(): @@ -163,6 +164,7 @@ def test_list_many_autoscaling_groups(): tags.should.contain({u'Value': 'TestTagValue1', u'Key': 'TestTagKey1'}) tags.should.contain({u'Value': 'TestGroup1', u'Key': 'aws:autoscaling:groupName'}) + @mock_autoscaling_deprecated def test_autoscaling_group_describe_filter(): conn = boto.connect_autoscale() @@ -493,7 +495,20 @@ def test_create_autoscaling_group_boto3(): LaunchConfigurationName='test_launch_configuration', MinSize=0, MaxSize=20, - DesiredCapacity=5 + DesiredCapacity=5, + Tags=[ + {'ResourceId': 'test_asg', + 'ResourceType': 'auto-scaling-group', + 'Key': 'propogated-tag-key', + 'Value': 'propogate-tag-value', + 'PropagateAtLaunch': True + }, + {'ResourceId': 'test_asg', + 'ResourceType': 'auto-scaling-group', + 'Key': 'not-propogated-tag-key', + 'Value': 'not-propogate-tag-value', + 'PropagateAtLaunch': False + }] ) response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) @@ -556,12 +571,14 @@ def test_autoscaling_taqs_update_boto3(): MinSize=0, MaxSize=20, DesiredCapacity=5, - Tags=[{ - "ResourceId": 'test_asg', - "Key": 'test_key', - "Value": 'test_value', - "PropagateAtLaunch": True - }] + Tags=[ + { + "ResourceId": 'test_asg', + "Key": 'test_key', + "Value": 'test_value', + "PropagateAtLaunch": True + }, + ] ) client.create_or_update_tags(Tags=[{ @@ -573,7 +590,7 @@ def test_autoscaling_taqs_update_boto3(): "ResourceId": 'test_asg', "Key": 'test_key2', "Value": 'test_value2', - "PropagateAtLaunch": True + "PropagateAtLaunch": False }]) response = client.describe_auto_scaling_groups( diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 87dcfd950..df696d879 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -382,7 +382,7 @@ def test_stack_elb_integration_with_update(): "Protocol": "HTTP", } ], - "Policies": {"Ref" : "AWS::NoValue"}, + "Policies": {"Ref": "AWS::NoValue"}, } }, }, @@ -536,8 +536,8 @@ def test_stack_security_groups(): @mock_autoscaling_deprecated() @mock_elb_deprecated() @mock_cloudformation_deprecated() +@mock_ec2_deprecated() def test_autoscaling_group_with_elb(): - web_setup_template = { "AWSTemplateFormatVersion": "2010-09-09", @@ -550,7 +550,17 @@ def test_autoscaling_group_with_elb(): "MinSize": "2", "MaxSize": "2", "DesiredCapacity": "2", - "LoadBalancerNames": [{"Ref": "my-elb"}] + "LoadBalancerNames": [{"Ref": "my-elb"}], + "Tags": [ + { + "Key": "propagated-test-tag", "Value": "propagated-test-tag-value", + "PropagateAtLaunch": True}, + { + "Key": "not-propagated-test-tag", + "Value": "not-propagated-test-tag-value", + "PropagateAtLaunch": False + } + ] }, }, @@ -611,7 +621,8 @@ def test_autoscaling_group_with_elb(): as_group_resource.physical_resource_id.should.contain("my-as-group") launch_config_resource = [ - resource for resource in resources if resource.resource_type == 'AWS::AutoScaling::LaunchConfiguration'][0] + resource for resource in resources if + resource.resource_type == 'AWS::AutoScaling::LaunchConfiguration'][0] launch_config_resource.physical_resource_id.should.contain( "my-launch-config") @@ -619,9 +630,20 @@ def test_autoscaling_group_with_elb(): 'AWS::ElasticLoadBalancing::LoadBalancer'][0] elb_resource.physical_resource_id.should.contain("my-elb") + # confirm the instances were created with the right tags + ec2_conn = boto.ec2.connect_to_region('us-west-1') + reservations = ec2_conn.get_all_reservations() + len(reservations).should.equal(1) + reservation = reservations[0] + len(reservation.instances).should.equal(2) + for instance in reservation.instances: + instance.tags['propagated-test-tag'].should.equal('propagated-test-tag-value') + instance.tags.keys().should_not.contain('not-propagated-test-tag') + @mock_autoscaling_deprecated() @mock_cloudformation_deprecated() +@mock_ec2_deprecated() def test_autoscaling_group_update(): asg_template = { "AWSTemplateFormatVersion": "2010-09-09", @@ -661,6 +683,16 @@ def test_autoscaling_group_update(): asg.desired_capacity.should.equal(2) asg_template['Resources']['my-as-group']['Properties']['MaxSize'] = 3 + asg_template['Resources']['my-as-group']['Properties']['Tags'] = [ + { + "Key": "propagated-test-tag", "Value": "propagated-test-tag-value", + "PropagateAtLaunch": True}, + { + "Key": "not-propagated-test-tag", + "Value": "not-propagated-test-tag-value", + "PropagateAtLaunch": False + } + ] asg_template_json = json.dumps(asg_template) conn.update_stack( "asg_stack", @@ -671,11 +703,22 @@ def test_autoscaling_group_update(): asg.max_size.should.equal(3) asg.desired_capacity.should.equal(2) + # confirm the instances were created with the right tags + ec2_conn = boto.ec2.connect_to_region('us-west-1') + reservations = ec2_conn.get_all_reservations() + running_instance_count = 0 + for res in reservations: + for instance in res.instances: + if instance.state == 'running': + running_instance_count += 1 + instance.tags['propagated-test-tag'].should.equal('propagated-test-tag-value') + instance.tags.keys().should_not.contain('not-propagated-test-tag') + running_instance_count.should.equal(2) + @mock_ec2_deprecated() @mock_cloudformation_deprecated() def test_vpc_single_instance_in_subnet(): - template_json = json.dumps(vpc_single_instance_in_subnet.template) conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( @@ -738,16 +781,16 @@ def test_rds_db_parameter_groups(): TemplateBody=template_json, Parameters=[{'ParameterKey': key, 'ParameterValue': value} for key, value in [ - ("DBInstanceIdentifier", "master_db"), - ("DBName", "my_db"), - ("DBUser", "my_user"), - ("DBPassword", "my_password"), - ("DBAllocatedStorage", "20"), - ("DBInstanceClass", "db.m1.medium"), - ("EC2SecurityGroup", "application"), - ("MultiAZ", "true"), - ] - ], + ("DBInstanceIdentifier", "master_db"), + ("DBName", "my_db"), + ("DBUser", "my_user"), + ("DBPassword", "my_password"), + ("DBAllocatedStorage", "20"), + ("DBInstanceClass", "db.m1.medium"), + ("EC2SecurityGroup", "application"), + ("MultiAZ", "true"), + ] + ], ) rds_conn = boto3.client('rds', region_name="us-west-1") @@ -758,8 +801,10 @@ def test_rds_db_parameter_groups(): 'DBParameterGroups'][0]['DBParameterGroupName'] found_cloudformation_set_parameter = False - for db_parameter in rds_conn.describe_db_parameters(DBParameterGroupName=db_parameter_group_name)['Parameters']: - if db_parameter['ParameterName'] == 'BACKLOG_QUEUE_LIMIT' and db_parameter['ParameterValue'] == '2048': + for db_parameter in rds_conn.describe_db_parameters(DBParameterGroupName=db_parameter_group_name)[ + 'Parameters']: + if db_parameter['ParameterName'] == 'BACKLOG_QUEUE_LIMIT' and db_parameter[ + 'ParameterValue'] == '2048': found_cloudformation_set_parameter = True found_cloudformation_set_parameter.should.equal(True) @@ -965,7 +1010,6 @@ def test_iam_roles(): @mock_ec2_deprecated() @mock_cloudformation_deprecated() def test_single_instance_with_ebs_volume(): - template_json = json.dumps(single_instance_with_ebs_volume.template) conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( @@ -1005,7 +1049,6 @@ def test_create_template_without_required_param(): @mock_ec2_deprecated() @mock_cloudformation_deprecated() def test_classic_eip(): - template_json = json.dumps(ec2_classic_eip.template) conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack("test_stack", template_body=template_json) @@ -1022,7 +1065,6 @@ def test_classic_eip(): @mock_ec2_deprecated() @mock_cloudformation_deprecated() def test_vpc_eip(): - template_json = json.dumps(vpc_eip.template) conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack("test_stack", template_body=template_json) @@ -1039,7 +1081,6 @@ def test_vpc_eip(): @mock_ec2_deprecated() @mock_cloudformation_deprecated() def test_fn_join(): - template_json = json.dumps(fn_join.template) conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack("test_stack", template_body=template_json) @@ -2009,25 +2050,25 @@ def test_stack_spot_fleet(): "TargetCapacity": 6, "AllocationStrategy": "diversified", "LaunchSpecifications": [ - { - "EbsOptimized": "false", - "InstanceType": 't2.small', - "ImageId": "ami-1234", - "SubnetId": subnet_id, - "WeightedCapacity": "2", - "SpotPrice": "0.13", - }, { - "EbsOptimized": "true", - "InstanceType": 't2.large', - "ImageId": "ami-1234", - "Monitoring": {"Enabled": "true"}, - "SecurityGroups": [{"GroupId": "sg-123"}], - "SubnetId": subnet_id, - "IamInstanceProfile": {"Arn": "arn:aws:iam::123456789012:role/fleet"}, - "WeightedCapacity": "4", - "SpotPrice": "10.00", - } + "EbsOptimized": "false", + "InstanceType": 't2.small', + "ImageId": "ami-1234", + "SubnetId": subnet_id, + "WeightedCapacity": "2", + "SpotPrice": "0.13", + }, + { + "EbsOptimized": "true", + "InstanceType": 't2.large', + "ImageId": "ami-1234", + "Monitoring": {"Enabled": "true"}, + "SecurityGroups": [{"GroupId": "sg-123"}], + "SubnetId": subnet_id, + "IamInstanceProfile": {"Arn": "arn:aws:iam::123456789012:role/fleet"}, + "WeightedCapacity": "4", + "SpotPrice": "10.00", + } ] } }