Merge pull request #162 from andresriancho/master
Fix block device mapping #160
This commit is contained in:
commit
195505948b
@ -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']
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user