From 5ba2b3cb9a305a9c821484759a1bd6c50b1cb208 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 7 May 2014 08:36:19 -0400 Subject: [PATCH 1/5] Added instance_type support to instances --- moto/ec2/models.py | 8 ++++++-- moto/ec2/responses/instances.py | 7 ++++--- tests/test_ec2/test_instances.py | 9 +++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 708756aff..f14772459 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -37,13 +37,14 @@ class InstanceState(object): class Instance(BotoInstance): - def __init__(self, image_id, user_data, security_groups): + def __init__(self, image_id, user_data, security_groups, **kwargs): super(Instance, self).__init__() self.id = random_instance_id() self.image_id = image_id self._state = InstanceState("running", 16) self.user_data = user_data self.security_groups = security_groups + self.instance_type = kwargs.get("instance_type", "m1.small") @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -57,6 +58,7 @@ class Instance(BotoInstance): user_data=properties.get('UserData'), count=1, security_group_names=group_names, + instance_type=properties.get("InstanceType", "m1.small"), ) return reservation.instances[0] @@ -96,7 +98,8 @@ class InstanceBackend(object): if instance.id == instance_id: return instance - def add_instances(self, image_id, count, user_data, security_group_names): + def add_instances(self, image_id, count, user_data, security_group_names, + **kwargs): new_reservation = Reservation() new_reservation.id = random_reservation_id() @@ -106,6 +109,7 @@ class InstanceBackend(object): image_id, user_data, security_groups, + **kwargs ) new_reservation.instances.append(new_instance) self.reservations[new_reservation.id] = new_reservation diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 13b23fd3f..280474136 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -33,7 +33,8 @@ class InstanceResponse(BaseResponse): image_id = self.querystring.get('ImageId')[0] user_data = self.querystring.get('UserData') security_group_names = self._get_multi_param('SecurityGroup') - new_reservation = ec2_backend.add_instances(image_id, min_count, user_data, security_group_names) + instance_type = self.querystring.get("InstanceType", ["m1.small"])[0] + new_reservation = ec2_backend.add_instances(image_id, min_count, user_data, security_group_names, instance_type=instance_type) template = Template(EC2_RUN_INSTANCES) return template.render(reservation=new_reservation) @@ -111,7 +112,7 @@ EC2_RUN_INSTANCES = """ Date: Wed, 7 May 2014 08:47:25 -0400 Subject: [PATCH 2/5] Added subnet_id support to instances --- moto/ec2/models.py | 2 ++ moto/ec2/responses/instances.py | 8 ++++++-- tests/test_ec2/test_instances.py | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index f14772459..91f16e4c9 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -45,6 +45,7 @@ class Instance(BotoInstance): self.user_data = user_data self.security_groups = security_groups self.instance_type = kwargs.get("instance_type", "m1.small") + self.subnet_id = kwargs.get("subnet_id") @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -59,6 +60,7 @@ class Instance(BotoInstance): count=1, security_group_names=group_names, instance_type=properties.get("InstanceType", "m1.small"), + subnet_id=properties.get("SubnetId") ) return reservation.instances[0] diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 280474136..a153713c7 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -34,7 +34,10 @@ class InstanceResponse(BaseResponse): user_data = self.querystring.get('UserData') security_group_names = self._get_multi_param('SecurityGroup') instance_type = self.querystring.get("InstanceType", ["m1.small"])[0] - new_reservation = ec2_backend.add_instances(image_id, min_count, user_data, security_group_names, instance_type=instance_type) + subnet_id = self.querystring.get("SubnetId", [None])[0] + new_reservation = ec2_backend.add_instances( + image_id, min_count, user_data, security_group_names, + instance_type=instance_type, subnet_id=subnet_id) template = Template(EC2_RUN_INSTANCES) return template.render(reservation=new_reservation) @@ -122,6 +125,7 @@ EC2_RUN_INSTANCES = """ Date: Wed, 7 May 2014 08:54:27 -0400 Subject: [PATCH 3/5] Added key_name support to instances --- moto/ec2/models.py | 4 +++- moto/ec2/responses/instances.py | 7 +++++-- tests/test_ec2/test_instances.py | 9 +++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 91f16e4c9..162338196 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -46,6 +46,7 @@ class Instance(BotoInstance): self.security_groups = security_groups self.instance_type = kwargs.get("instance_type", "m1.small") self.subnet_id = kwargs.get("subnet_id") + self.key_name = kwargs.get("key_name") @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -60,7 +61,8 @@ class Instance(BotoInstance): count=1, security_group_names=group_names, instance_type=properties.get("InstanceType", "m1.small"), - subnet_id=properties.get("SubnetId") + subnet_id=properties.get("SubnetId"), + key_name=properties.get("KeyName"), ) return reservation.instances[0] diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index a153713c7..d3c2136fd 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -35,9 +35,11 @@ class InstanceResponse(BaseResponse): security_group_names = self._get_multi_param('SecurityGroup') instance_type = self.querystring.get("InstanceType", ["m1.small"])[0] subnet_id = self.querystring.get("SubnetId", [None])[0] + key_name = self.querystring.get("KeyName", [None])[0] new_reservation = ec2_backend.add_instances( image_id, min_count, user_data, security_group_names, - instance_type=instance_type, subnet_id=subnet_id) + instance_type=instance_type, subnet_id=subnet_id, + key_name=key_name) template = Template(EC2_RUN_INSTANCES) return template.render(reservation=new_reservation) @@ -114,6 +116,7 @@ EC2_RUN_INSTANCES = """ Date: Wed, 7 May 2014 09:16:28 -0400 Subject: [PATCH 4/5] Allow passing security groups by ID when creating instances --- moto/ec2/models.py | 5 ++++- moto/ec2/responses/instances.py | 6 ++++-- tests/test_ec2/test_instances.py | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 162338196..d40c21f57 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -107,7 +107,10 @@ class InstanceBackend(object): new_reservation = Reservation() new_reservation.id = random_reservation_id() - security_groups = [self.get_security_group_from_name(name) for name in security_group_names] + security_groups = [self.get_security_group_from_name(name) + for name in security_group_names] + security_groups.extend(self.get_security_group_from_id(sg_id) + for sg_id in kwargs.pop("security_group_ids", [])) for index in range(count): new_instance = Instance( image_id, diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index d3c2136fd..3b442eaea 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -9,7 +9,8 @@ from moto.ec2.exceptions import InvalidIdError class InstanceResponse(BaseResponse): def _get_multi_param(self, param_prefix): - return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)] + return [value[0] for key, value in self.querystring.items() + if key.startswith(param_prefix + ".")] def describe_instances(self): instance_ids = instance_ids_from_querystring(self.querystring) @@ -33,13 +34,14 @@ class InstanceResponse(BaseResponse): image_id = self.querystring.get('ImageId')[0] user_data = self.querystring.get('UserData') security_group_names = self._get_multi_param('SecurityGroup') + security_group_ids = self._get_multi_param('SecurityGroupId') instance_type = self.querystring.get("InstanceType", ["m1.small"])[0] subnet_id = self.querystring.get("SubnetId", [None])[0] key_name = self.querystring.get("KeyName", [None])[0] new_reservation = ec2_backend.add_instances( image_id, min_count, user_data, security_group_names, instance_type=instance_type, subnet_id=subnet_id, - key_name=key_name) + key_name=key_name, security_group_ids=security_group_ids) template = Template(EC2_RUN_INSTANCES) return template.render(reservation=new_reservation) diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index 3d52a76cc..9a0cd19be 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -173,11 +173,25 @@ def test_user_data_with_run_instance(): @mock_ec2 -def test_run_instance_with_security_group(): +def test_run_instance_with_security_group_name(): conn = boto.connect_ec2('the_key', 'the_secret') group = conn.create_security_group('group1', "some description") - reservation = conn.run_instances('ami-1234abcd', security_groups=['group1']) + reservation = conn.run_instances('ami-1234abcd', + security_groups=['group1']) + instance = reservation.instances[0] + + instance.groups[0].id.should.equal(group.id) + instance.groups[0].name.should.equal("group1") + + +@mock_ec2 +def test_run_instance_with_security_group_id(): + conn = boto.connect_ec2('the_key', 'the_secret') + group = conn.create_security_group('group1', "some description") + + reservation = conn.run_instances('ami-1234abcd', + security_group_ids=[group.id]) instance = reservation.instances[0] instance.groups[0].id.should.equal(group.id) From fab37942c49bbc97d052df4f5f37e390d3d7d96d Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 8 May 2014 10:41:28 -0400 Subject: [PATCH 5/5] Consistent _get_multi_param() function in responses This abstracts _get_multi_param() into BaseResponse and makes it always ensure that the string it has been given ends with a '.'. It had been implemented in three different places, and in use it rarely postpended a trailing period, which could make it match parameters it shouldn't have. --- moto/autoscaling/responses.py | 9 +-------- moto/core/responses.py | 8 ++++++++ moto/ec2/responses/instances.py | 4 ---- moto/ec2/responses/spot_instances.py | 5 +---- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py index 32eb7b301..267db1f91 100644 --- a/moto/autoscaling/responses.py +++ b/moto/autoscaling/responses.py @@ -6,18 +6,11 @@ from .models import autoscaling_backend class AutoScalingResponse(BaseResponse): - - def _get_param(self, param_name): - return self.querystring.get(param_name, [None])[0] - def _get_int_param(self, param_name): value = self._get_param(param_name) if value is not None: return int(value) - 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 @@ -43,7 +36,7 @@ class AutoScalingResponse(BaseResponse): name=self._get_param('LaunchConfigurationName'), image_id=self._get_param('ImageId'), key_name=self._get_param('KeyName'), - security_groups=self._get_multi_param('SecurityGroups.member.'), + security_groups=self._get_multi_param('SecurityGroups.member'), user_data=self._get_param('UserData'), instance_type=self._get_param('InstanceType'), instance_monitoring=instance_monitoring, diff --git a/moto/core/responses.py b/moto/core/responses.py index 8e6c5bfcc..fee641843 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -66,6 +66,14 @@ class BaseResponse(object): def _get_param(self, param_name): return self.querystring.get(param_name, [None])[0] + def _get_multi_param(self, param_prefix): + if param_prefix.endswith("."): + prefix = param_prefix + else: + prefix = param_prefix + "." + return [value[0] for key, value in self.querystring.items() + if key.startswith(prefix)] + def metadata_response(request, full_url, headers): """ diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 3b442eaea..12b52607f 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -8,10 +8,6 @@ from moto.ec2.exceptions import InvalidIdError class InstanceResponse(BaseResponse): - def _get_multi_param(self, param_prefix): - return [value[0] for key, value in self.querystring.items() - if key.startswith(param_prefix + ".")] - def describe_instances(self): instance_ids = instance_ids_from_querystring(self.querystring) if instance_ids: diff --git a/moto/ec2/responses/spot_instances.py b/moto/ec2/responses/spot_instances.py index 2c3d61a40..5ce8ee122 100644 --- a/moto/ec2/responses/spot_instances.py +++ b/moto/ec2/responses/spot_instances.py @@ -13,9 +13,6 @@ class SpotInstances(BaseResponse): if value is not None: return int(value) - def _get_multi_param(self, param_prefix): - return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)] - def cancel_spot_instance_requests(self): request_ids = self._get_multi_param('SpotInstanceRequestId') requests = ec2_backend.cancel_spot_instance_requests(request_ids) @@ -49,7 +46,7 @@ class SpotInstances(BaseResponse): launch_group = self._get_param('LaunchGroup') availability_zone_group = self._get_param('AvailabilityZoneGroup') key_name = self._get_param('LaunchSpecification.KeyName') - security_groups = self._get_multi_param('LaunchSpecification.SecurityGroup.') + security_groups = self._get_multi_param('LaunchSpecification.SecurityGroup') user_data = self._get_param('LaunchSpecification.UserData') instance_type = self._get_param('LaunchSpecification.InstanceType') placement = self._get_param('LaunchSpecification.Placement.AvailabilityZone')