diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 3e384288d..3d3e4d5fb 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -3,6 +3,7 @@ import itertools from collections import defaultdict from boto.ec2.instance import Instance as BotoInstance, Reservation +from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest from boto.ec2.launchspecification import LaunchSpecification @@ -62,6 +63,9 @@ class Instance(BotoInstance, TaggedEC2Instance): self.subnet_id = kwargs.get("subnet_id") self.key_name = kwargs.get("key_name") + self.block_device_mapping = BlockDeviceMapping() + self.block_device_mapping['/dev/sda1'] = BlockDeviceType() + @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): properties = cloudformation_json['Properties'] diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 12b52607f..106702c14 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -66,7 +66,8 @@ class InstanceResponse(BaseResponse): return template.render(instances=instances) def describe_instance_attribute(self): - # TODO this and modify below should raise IncorrectInstanceState if instance not in stopped state + # TODO this and modify below should raise IncorrectInstanceState if + # instance not in stopped state attribute = self.querystring.get("Attribute")[0] key = camelcase_to_underscores(attribute) instance_ids = instance_ids_from_querystring(self.querystring) @@ -76,6 +77,61 @@ class InstanceResponse(BaseResponse): return template.render(instance=instance, attribute=attribute, value=value) def modify_instance_attribute(self): + handlers = [self._dot_value_instance_attribute_handler, + self._block_device_mapping_handler] + + for handler in handlers: + success = handler() + if success: + return success + + msg = "This specific call to ModifyInstanceAttribute has not been" \ + " implemented in Moto yet. Feel free to open an issue at" \ + " https://github.com/spulec/moto/issues" + raise NotImplementedError(msg) + + def _block_device_mapping_handler(self): + """ + Handles requests which are generated by code similar to: + + instance.modify_attribute('blockDeviceMapping', {'/dev/sda1': True}) + + The querystring contains information similar to: + + BlockDeviceMapping.1.Ebs.DeleteOnTermination : ['true'] + BlockDeviceMapping.1.DeviceName : ['/dev/sda1'] + + For now we only support the "BlockDeviceMapping.1.Ebs.DeleteOnTermination" + configuration, but it should be trivial to add anything else. + """ + mapping_counter = 1 + mapping_device_name_fmt = 'BlockDeviceMapping.%s.DeviceName' + mapping_del_on_term_fmt = 'BlockDeviceMapping.%s.Ebs.DeleteOnTermination' + + while True: + mapping_device_name = mapping_device_name_fmt % mapping_counter + if mapping_device_name not in self.querystring.keys(): + break + + mapping_del_on_term = mapping_del_on_term_fmt % mapping_counter + del_on_term_value_str = self.querystring[mapping_del_on_term][0] + del_on_term_value = True if 'true' == del_on_term_value_str else False + device_name_value = self.querystring[mapping_device_name][0] + + instance_ids = instance_ids_from_querystring(self.querystring) + instance_id = instance_ids[0] + instance = ec2_backend.get_instance(instance_id) + + block_device_type = instance.block_device_mapping[device_name_value] + block_device_type.delete_on_termination = del_on_term_value + + # +1 for the next device + mapping_counter += 1 + + if mapping_counter > 1: + return EC2_MODIFY_INSTANCE_ATTRIBUTE + + def _dot_value_instance_attribute_handler(self): attribute_key = None for key, value in self.querystring.iteritems(): if '.Value' in key: @@ -84,6 +140,7 @@ class InstanceResponse(BaseResponse): if not attribute_key: return + value = self.querystring.get(attribute_key)[0] normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0]) instance_ids = instance_ids_from_querystring(self.querystring) diff --git a/tests/test_ec2/test_elastic_block_store.py b/tests/test_ec2/test_elastic_block_store.py index c60ce0bf0..2c1edddd4 100644 --- a/tests/test_ec2/test_elastic_block_store.py +++ b/tests/test_ec2/test_elastic_block_store.py @@ -3,6 +3,7 @@ from boto.exception import EC2ResponseError import sure # noqa from moto import mock_ec2 +from moto.ec2.models import ec2_backend @mock_ec2 @@ -91,4 +92,9 @@ def test_modify_attribute_blockDeviceMapping(): reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] - instance.modify_attribute('blockDeviceMapping', {'/dev/sda1': True}) \ No newline at end of file + + instance.modify_attribute('blockDeviceMapping', {'/dev/sda1': True}) + + instance = ec2_backend.get_instance(instance.id) + instance.block_device_mapping.should.have.key('/dev/sda1') + instance.block_device_mapping['/dev/sda1'].delete_on_termination.should.be(True)