From a0e48a6cf51f8b2c1faf55102298748393514edd Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Tue, 25 Feb 2014 14:34:53 -0500 Subject: [PATCH 1/4] [Block Device] Add block device mapping to launch config backend --- moto/autoscaling/models.py | 38 ++++++++++- moto/autoscaling/responses.py | 46 ++++++++++++- .../test_launch_configurations.py | 67 +++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index a8033709..d4d5c794 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -1,3 +1,4 @@ +from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping from moto.core import BaseBackend from moto.ec2 import ec2_backend @@ -29,7 +30,7 @@ class FakeScalingPolicy(object): class FakeLaunchConfiguration(object): def __init__(self, name, image_id, key_name, security_groups, user_data, instance_type, instance_monitoring, instance_profile_name, - spot_price, ebs_optimized, associate_public_ip_address): + spot_price, ebs_optimized, associate_public_ip_address, block_device_mapping_dict): self.name = name self.image_id = image_id self.key_name = key_name @@ -40,6 +41,7 @@ class FakeLaunchConfiguration(object): self.instance_profile_name = instance_profile_name self.spot_price = spot_price self.ebs_optimized = ebs_optimized +<<<<<<< HEAD self.associate_public_ip_address = associate_public_ip_address @classmethod @@ -66,6 +68,16 @@ class FakeLaunchConfiguration(object): @property def physical_resource_id(self): return self.name +======= + self.block_device_mapping_dict = block_device_mapping_dict + + @property + def block_device_mappings(self): + if not self.block_device_mapping_dict: + return None + else: + return self._parse_block_device_mappings() +>>>>>>> [Block Device] Add block device mapping to launch config backend @property def instance_monitoring_enabled(self): @@ -73,6 +85,22 @@ class FakeLaunchConfiguration(object): return 'true' return 'false' + def _parse_block_device_mappings(self): + block_device_map = BlockDeviceMapping() + for mapping in self.block_device_mapping_dict: + block_type = BlockDeviceType() + mount_point = mapping.get('device_name') + if 'ephemeral' in mapping.get('virtual_name', ''): + block_type.ephemeral_name = mapping.get('virtual_name') + else: + block_type.volume_type = mapping.get('ebs._volume_type') + block_type.snapshot_id = mapping.get('ebs._snapshot_id') + block_type.delete_on_termination = mapping.get('ebs._delete_on_termination') + block_type.size = mapping.get('ebs._volume_size') + block_type.iops = mapping.get('ebs._iops') + block_device_map[mount_point] = block_type + return block_device_map + class FakeAutoScalingGroup(object): def __init__(self, name, availability_zones, desired_capacity, max_size, @@ -184,7 +212,11 @@ class AutoScalingBackend(BaseBackend): def create_launch_configuration(self, name, image_id, key_name, security_groups, user_data, instance_type, instance_monitoring, instance_profile_name, +<<<<<<< HEAD spot_price, ebs_optimized, associate_public_ip_address): +======= + spot_price, ebs_optimized, block_device_mappings): +>>>>>>> [Block Device] Add block device mapping to launch config backend launch_configuration = FakeLaunchConfiguration( name=name, image_id=image_id, @@ -196,7 +228,11 @@ class AutoScalingBackend(BaseBackend): instance_profile_name=instance_profile_name, spot_price=spot_price, ebs_optimized=ebs_optimized, +<<<<<<< HEAD associate_public_ip_address=associate_public_ip_address, +======= + block_device_mapping_dict=block_device_mappings, +>>>>>>> [Block Device] Add block device mapping to launch config backend ) self.launch_configurations[name] = launch_configuration return launch_configuration diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py index a881919e..32eb7b30 100644 --- a/moto/autoscaling/responses.py +++ b/moto/autoscaling/responses.py @@ -1,6 +1,7 @@ from jinja2 import Template from moto.core.responses import BaseResponse +from moto.core.utils import camelcase_to_underscores from .models import autoscaling_backend @@ -17,6 +18,21 @@ class AutoScalingResponse(BaseResponse): def _get_multi_param(self, param_prefix): return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)] + def _get_list_prefix(self, param_prefix): + results = [] + param_index = 1 + while True: + index_prefix = "{0}.{1}.".format(param_prefix, param_index) + new_items = {} + for key, value in self.querystring.items(): + if key.startswith(index_prefix): + new_items[camelcase_to_underscores(key.replace(index_prefix, ""))] = value[0] + if not new_items: + break + results.append(new_items) + param_index += 1 + return results + def create_launch_configuration(self): instance_monitoring_string = self._get_param('InstanceMonitoring.Enabled') if instance_monitoring_string == 'true': @@ -35,6 +51,7 @@ class AutoScalingResponse(BaseResponse): spot_price=self._get_param('SpotPrice'), ebs_optimized=self._get_param('EbsOptimized'), associate_public_ip_address=self._get_param("AssociatePublicIpAddress"), + block_device_mappings=self._get_list_prefix('BlockDeviceMappings.member') ) template = Template(CREATE_LAUNCH_CONFIGURATION_TEMPLATE) return template.render() @@ -173,7 +190,34 @@ DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE = """m1.small arn:aws:autoscaling:us-east-1:803981987763:launchConfiguration: 9dbbbf87-6141-428a-a409-0752edbe6cad:launchConfigurationName/my-test-lc - + {% if launch_configuration.block_device_mappings %} + + {% for mount_point, mapping in launch_configuration.block_device_mappings.iteritems() %} + + {{ mount_point }} + {% if mapping.ephemeral_name %} + {{ mapping.ephemeral_name }} + {% else %} + + {% if mapping.snapshot_id %} + {{ mapping.snapshot_id }} + {% endif %} + {% if mapping.size %} + {{ mapping.size }} + {% endif %} + {% if mapping.iops %} + {{ mapping.iops }} + {% endif %} + {{ mapping.delete_on_termination }} + {{ mapping.volume_type }} + + {% endif %} + + {% endfor %} + + {% else %} + + {% endif %} {{ launch_configuration.image_id }} {% if launch_configuration.key_name %} {{ launch_configuration.key_name }} diff --git a/tests/test_autoscaling/test_launch_configurations.py b/tests/test_autoscaling/test_launch_configurations.py index fcbc2f6d..10f7dbc4 100644 --- a/tests/test_autoscaling/test_launch_configurations.py +++ b/tests/test_autoscaling/test_launch_configurations.py @@ -1,5 +1,6 @@ import boto from boto.ec2.autoscale.launchconfig import LaunchConfiguration +from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping import sure # noqa @@ -35,6 +36,72 @@ def test_create_launch_configuration(): launch_config.spot_price.should.equal(0.1) +@mock_autoscaling +def test_create_launch_configuration_with_block_device_mappings(): + block_device_mapping = BlockDeviceMapping() + + ephemeral_drive = BlockDeviceType() + ephemeral_drive.ephemeral_name = 'ephemeral0' + block_device_mapping['/dev/xvdb'] = ephemeral_drive + + snapshot_drive = BlockDeviceType() + snapshot_drive.snapshot_id = "snap-1234abcd" + snapshot_drive.volume_type = "standard" + block_device_mapping['/dev/xvdp'] = snapshot_drive + + ebs_drive = BlockDeviceType() + ebs_drive.volume_type = "io1" + ebs_drive.size = 100 + ebs_drive.iops = 1000 + ebs_drive.delete_on_termination = False + block_device_mapping['/dev/xvdh'] = ebs_drive + + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + key_name='the_keys', + security_groups=["default", "default2"], + user_data="This is some user_data", + instance_monitoring=True, + instance_profile_name='arn:aws:iam::123456789012:instance-profile/testing', + spot_price=0.1, + block_device_mappings=[block_device_mapping] + ) + conn.create_launch_configuration(config) + + launch_config = conn.get_all_launch_configurations()[0] + launch_config.name.should.equal('tester') + launch_config.image_id.should.equal('ami-abcd1234') + launch_config.instance_type.should.equal('m1.small') + launch_config.key_name.should.equal('the_keys') + set(launch_config.security_groups).should.equal(set(['default', 'default2'])) + launch_config.user_data.should.equal("This is some user_data") + launch_config.instance_monitoring.enabled.should.equal('true') + launch_config.instance_profile_name.should.equal('arn:aws:iam::123456789012:instance-profile/testing') + launch_config.spot_price.should.equal(0.1) + len(launch_config.block_device_mappings).should.equal(3) + + # Unsure why boto returns results in a different format than it takes them + # returned_mapping = BlockDeviceMapping() + # for mapping in launch_config.block_device_mappings: + # returned_mapping[mapping.device_name] = mapping + + # Broken due to Boto bug + # set(returned_mapping.keys()).should.equal(set(['/dev/xvdb', '/dev/xvdp', '/dev/xvdh'])) + + # returned_mapping['/dev/xvdh'].ebs.iops.should.equal(1000) + # returned_mapping['/dev/xvdh'].ebs.volume_size.should.equal(100) + # returned_mapping['/dev/xvdh'].ebs.volume_type.shoud.equal("io1") + # returned_mapping['/dev/xvdh'].ebs.delete_on_termination.shoud.be.false + + # returned_mapping['/dev/xvdp'].ebs.snapshot_id.should.equal("snap-1234abcd") + # returned_mapping['/dev/xvdp'].ebs.volume_type.shoud.equal("standard") + + # returned_mapping['/dev/xvdb'].ephemeral_name.should.equal('ephemeral0') + + @requires_boto_gte("2.12") @mock_autoscaling def test_create_launch_configuration_for_2_12(): From 81a979cd1d6a95f877b136b324c920b8b01b5933 Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Fri, 28 Feb 2014 14:59:09 -0500 Subject: [PATCH 2/4] Switch to fork for now --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 96e1d08d..246816c2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -r requirements.txt flask -boto +https://github.com/andrewgross/boto/tarball/52543eebe88eae6e3f403d3692e5600188cf304f httpretty From dbe17d059fb03052a3b0c7e33827bfb2f84ad167 Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Fri, 25 Apr 2014 15:44:55 -0400 Subject: [PATCH 3/4] Fix some merge issues, add block device parsing --- moto/autoscaling/models.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index d4d5c794..fc7c82cc 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -41,8 +41,8 @@ class FakeLaunchConfiguration(object): self.instance_profile_name = instance_profile_name self.spot_price = spot_price self.ebs_optimized = ebs_optimized -<<<<<<< HEAD self.associate_public_ip_address = associate_public_ip_address + self.block_device_mapping_dict = block_device_mapping_dict @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -62,14 +62,13 @@ class FakeLaunchConfiguration(object): spot_price=properties.get("SpotPrice"), ebs_optimized=properties.get("EbsOptimized"), associate_public_ip_address=properties.get("AssociatePublicIpAddress"), + block_device_mappings=properties.get("BlockDeviceMapping.member") ) return config @property def physical_resource_id(self): return self.name -======= - self.block_device_mapping_dict = block_device_mapping_dict @property def block_device_mappings(self): @@ -77,7 +76,6 @@ class FakeLaunchConfiguration(object): return None else: return self._parse_block_device_mappings() ->>>>>>> [Block Device] Add block device mapping to launch config backend @property def instance_monitoring_enabled(self): @@ -212,11 +210,7 @@ class AutoScalingBackend(BaseBackend): def create_launch_configuration(self, name, image_id, key_name, security_groups, user_data, instance_type, instance_monitoring, instance_profile_name, -<<<<<<< HEAD - spot_price, ebs_optimized, associate_public_ip_address): -======= - spot_price, ebs_optimized, block_device_mappings): ->>>>>>> [Block Device] Add block device mapping to launch config backend + spot_price, ebs_optimized, associate_public_ip_address, block_device_mappings): launch_configuration = FakeLaunchConfiguration( name=name, image_id=image_id, @@ -228,11 +222,8 @@ class AutoScalingBackend(BaseBackend): instance_profile_name=instance_profile_name, spot_price=spot_price, ebs_optimized=ebs_optimized, -<<<<<<< HEAD associate_public_ip_address=associate_public_ip_address, -======= block_device_mapping_dict=block_device_mappings, ->>>>>>> [Block Device] Add block device mapping to launch config backend ) self.launch_configurations[name] = launch_configuration return launch_configuration From ce31b0200a76b3a884af4d4b95f1c4a7179940b3 Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Fri, 25 Apr 2014 16:18:26 -0400 Subject: [PATCH 4/4] Fix tests when using a working boto version --- requirements-dev.txt | 2 +- .../test_launch_configurations.py | 25 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 246816c2..96e1d08d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -r requirements.txt flask -https://github.com/andrewgross/boto/tarball/52543eebe88eae6e3f403d3692e5600188cf304f +boto httpretty diff --git a/tests/test_autoscaling/test_launch_configurations.py b/tests/test_autoscaling/test_launch_configurations.py index 10f7dbc4..245adb55 100644 --- a/tests/test_autoscaling/test_launch_configurations.py +++ b/tests/test_autoscaling/test_launch_configurations.py @@ -36,6 +36,7 @@ def test_create_launch_configuration(): launch_config.spot_price.should.equal(0.1) +@requires_boto_gte("2.27.0") @mock_autoscaling def test_create_launch_configuration_with_block_device_mappings(): block_device_mapping = BlockDeviceMapping() @@ -56,7 +57,7 @@ def test_create_launch_configuration_with_block_device_mappings(): ebs_drive.delete_on_termination = False block_device_mapping['/dev/xvdh'] = ebs_drive - conn = boto.connect_autoscale() + conn = boto.connect_autoscale(use_block_device_types=True) config = LaunchConfiguration( name='tester', image_id='ami-abcd1234', @@ -83,23 +84,19 @@ def test_create_launch_configuration_with_block_device_mappings(): launch_config.spot_price.should.equal(0.1) len(launch_config.block_device_mappings).should.equal(3) - # Unsure why boto returns results in a different format than it takes them - # returned_mapping = BlockDeviceMapping() - # for mapping in launch_config.block_device_mappings: - # returned_mapping[mapping.device_name] = mapping + returned_mapping = launch_config.block_device_mappings - # Broken due to Boto bug - # set(returned_mapping.keys()).should.equal(set(['/dev/xvdb', '/dev/xvdp', '/dev/xvdh'])) + set(returned_mapping.keys()).should.equal(set(['/dev/xvdb', '/dev/xvdp', '/dev/xvdh'])) - # returned_mapping['/dev/xvdh'].ebs.iops.should.equal(1000) - # returned_mapping['/dev/xvdh'].ebs.volume_size.should.equal(100) - # returned_mapping['/dev/xvdh'].ebs.volume_type.shoud.equal("io1") - # returned_mapping['/dev/xvdh'].ebs.delete_on_termination.shoud.be.false + returned_mapping['/dev/xvdh'].iops.should.equal(1000) + returned_mapping['/dev/xvdh'].size.should.equal(100) + returned_mapping['/dev/xvdh'].volume_type.should.equal("io1") + returned_mapping['/dev/xvdh'].delete_on_termination.should.be.false - # returned_mapping['/dev/xvdp'].ebs.snapshot_id.should.equal("snap-1234abcd") - # returned_mapping['/dev/xvdp'].ebs.volume_type.shoud.equal("standard") + returned_mapping['/dev/xvdp'].snapshot_id.should.equal("snap-1234abcd") + returned_mapping['/dev/xvdp'].volume_type.should.equal("standard") - # returned_mapping['/dev/xvdb'].ephemeral_name.should.equal('ephemeral0') + returned_mapping['/dev/xvdb'].ephemeral_name.should.equal('ephemeral0') @requires_boto_gte("2.12")