Merge pull request #162 from andresriancho/master

Fix block device mapping #160
This commit is contained in:
Steve Pulec 2014-08-20 07:39:24 -04:00
commit 195505948b
3 changed files with 84 additions and 1 deletions

View File

@ -3,6 +3,7 @@ import itertools
from collections import defaultdict from collections import defaultdict
from boto.ec2.instance import Instance as BotoInstance, Reservation 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.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
from boto.ec2.launchspecification import LaunchSpecification from boto.ec2.launchspecification import LaunchSpecification
@ -65,6 +66,9 @@ class Instance(BotoInstance, TaggedEC2Instance):
self.subnet_id = kwargs.get("subnet_id") self.subnet_id = kwargs.get("subnet_id")
self.key_name = kwargs.get("key_name") self.key_name = kwargs.get("key_name")
self.block_device_mapping = BlockDeviceMapping()
self.block_device_mapping['/dev/sda1'] = BlockDeviceType()
@classmethod @classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json): def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
properties = cloudformation_json['Properties'] properties = cloudformation_json['Properties']

View File

@ -66,7 +66,8 @@ class InstanceResponse(BaseResponse):
return template.render(instances=instances) return template.render(instances=instances)
def describe_instance_attribute(self): 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] attribute = self.querystring.get("Attribute")[0]
key = camelcase_to_underscores(attribute) key = camelcase_to_underscores(attribute)
instance_ids = instance_ids_from_querystring(self.querystring) instance_ids = instance_ids_from_querystring(self.querystring)
@ -76,6 +77,61 @@ class InstanceResponse(BaseResponse):
return template.render(instance=instance, attribute=attribute, value=value) return template.render(instance=instance, attribute=attribute, value=value)
def modify_instance_attribute(self): 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 attribute_key = None
for key, value in self.querystring.iteritems(): for key, value in self.querystring.iteritems():
if '.Value' in key: if '.Value' in key:
@ -84,6 +140,7 @@ class InstanceResponse(BaseResponse):
if not attribute_key: if not attribute_key:
return return
value = self.querystring.get(attribute_key)[0] value = self.querystring.get(attribute_key)[0]
normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0]) normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0])
instance_ids = instance_ids_from_querystring(self.querystring) instance_ids = instance_ids_from_querystring(self.querystring)

View File

@ -3,6 +3,7 @@ from boto.exception import EC2ResponseError
import sure # noqa import sure # noqa
from moto import mock_ec2 from moto import mock_ec2
from moto.ec2.models import ec2_backend
@mock_ec2 @mock_ec2
@ -76,3 +77,24 @@ def test_create_snapshot():
# Deleting something that was already deleted should throw an error # Deleting something that was already deleted should throw an error
snapshot.delete.when.called_with().should.throw(EC2ResponseError) snapshot.delete.when.called_with().should.throw(EC2ResponseError)
@mock_ec2
def test_modify_attribute_blockDeviceMapping():
"""
Reproduces the missing feature explained at [0], where we want to mock a
call to modify an instance attribute of type: blockDeviceMapping.
[0] https://github.com/spulec/moto/issues/160
"""
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
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)