Merge pull request #384 from rocky4570/volmods

volmods
This commit is contained in:
Steve Pulec 2015-08-01 19:37:25 -04:00
commit 4543db567c
5 changed files with 116 additions and 21 deletions

View File

@ -1,17 +1,17 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import defaultdict import boto
import copy import copy
from datetime import datetime
import itertools import itertools
import re import re
import six
import boto from collections import defaultdict
from datetime import datetime
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.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
import six
from moto.core import BaseBackend from moto.core import BaseBackend
from moto.core.models import Model from moto.core.models import Model
@ -97,6 +97,9 @@ from .utils import (
) )
def utc_date_and_time():
return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
def validate_resource_ids(resource_ids): def validate_resource_ids(resource_ids):
for resource_id in resource_ids: for resource_id in resource_ids:
if not is_valid_resource_id(resource_id): if not is_valid_resource_id(resource_id):
@ -309,14 +312,17 @@ class Instance(BotoInstance, TaggedEC2Resource):
in_ec2_classic = not bool(self.subnet_id) in_ec2_classic = not bool(self.subnet_id)
self.key_name = kwargs.get("key_name") self.key_name = kwargs.get("key_name")
self.source_dest_check = "true" self.source_dest_check = "true"
self.launch_time = datetime.utcnow().isoformat() self.launch_time = utc_date_and_time()
associate_public_ip = kwargs.get("associate_public_ip", False) associate_public_ip = kwargs.get("associate_public_ip", False)
if in_ec2_classic: if in_ec2_classic:
# If we are in EC2-Classic, autoassign a public IP # If we are in EC2-Classic, autoassign a public IP
associate_public_ip = True associate_public_ip = True
self.block_device_mapping = BlockDeviceMapping() self.block_device_mapping = BlockDeviceMapping()
self.block_device_mapping['/dev/sda1'] = BlockDeviceType(volume_id=random_volume_id()) # Default have an instance with root volume should you not wish to override with attach volume cmd.
# However this is a ghost volume and wont show up in get_all_volumes or snapshot-able.
self.block_device_mapping['/dev/sda1'] = BlockDeviceType(volume_id=random_volume_id(), status='attached',
attach_time=utc_date_and_time())
amis = self.ec2_backend.describe_images(filters={'image-id': image_id}) amis = self.ec2_backend.describe_images(filters={'image-id': image_id})
ami = amis[0] if amis else None ami = amis[0] if amis else None
@ -343,6 +349,10 @@ class Instance(BotoInstance, TaggedEC2Resource):
private_ip=kwargs.get("private_ip"), private_ip=kwargs.get("private_ip"),
associate_public_ip=associate_public_ip) associate_public_ip=associate_public_ip)
@property
def get_block_device_mapping(self):
return self.block_device_mapping.items()
@property @property
def private_ip(self): def private_ip(self):
return self.nics[0].private_ip_address return self.nics[0].private_ip_address
@ -1349,6 +1359,7 @@ class SecurityGroupIngress(object):
class VolumeAttachment(object): class VolumeAttachment(object):
def __init__(self, volume, instance, device): def __init__(self, volume, instance, device):
self.volume = volume self.volume = volume
self.attach_time = utc_date_and_time()
self.instance = instance self.instance = instance
self.device = device self.device = device
@ -1373,6 +1384,7 @@ class Volume(TaggedEC2Resource):
self.id = volume_id self.id = volume_id
self.size = size self.size = size
self.zone = zone self.zone = zone
self.create_time = utc_date_and_time()
self.attachment = None self.attachment = None
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
@ -1404,6 +1416,7 @@ class Snapshot(TaggedEC2Resource):
self.id = snapshot_id self.id = snapshot_id
self.volume = volume self.volume = volume
self.description = description self.description = description
self.start_time = utc_date_and_time()
self.create_volume_permission_groups = set() self.create_volume_permission_groups = set()
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
@ -1444,6 +1457,13 @@ class EBSBackend(object):
return False return False
volume.attachment = VolumeAttachment(volume, instance, device_path) volume.attachment = VolumeAttachment(volume, instance, device_path)
# Modify instance to capture mount of block device.
bdt = BlockDeviceType(volume_id=volume_id, status=volume.status, size=volume.size,
attach_time=utc_date_and_time())
try:
instance.block_device_mapping[device_path] = bdt
except:
instance.block_device_mapping.setdefault(device_path, bdt)
return volume.attachment return volume.attachment
def detach_volume(self, volume_id, instance_id, device_path): def detach_volume(self, volume_id, instance_id, device_path):

View File

@ -42,12 +42,22 @@ class ElasticBlockStore(BaseResponse):
return DELETE_VOLUME_RESPONSE return DELETE_VOLUME_RESPONSE
def describe_snapshots(self): def describe_snapshots(self):
# querystring for multiple snapshotids results in SnapshotId.1, SnapshotId.2 etc
snapshot_ids = ','.join([','.join(s[1]) for s in self.querystring.items() if 'SnapshotId' in s[0]])
snapshots = self.ec2_backend.describe_snapshots() snapshots = self.ec2_backend.describe_snapshots()
# Describe snapshots to handle filter on snapshot_ids
snapshots = [s for s in snapshots if s.id in snapshot_ids] if snapshot_ids else snapshots
# snapshots = self.ec2_backend.describe_snapshots()
template = self.response_template(DESCRIBE_SNAPSHOTS_RESPONSE) template = self.response_template(DESCRIBE_SNAPSHOTS_RESPONSE)
return template.render(snapshots=snapshots) return template.render(snapshots=snapshots)
def describe_volumes(self): def describe_volumes(self):
# querystring for multiple volumeids results in VolumeId.1, VolumeId.2 etc
volume_ids = ','.join([','.join(v[1]) for v in self.querystring.items() if 'VolumeId' in v[0]])
volumes = self.ec2_backend.describe_volumes() volumes = self.ec2_backend.describe_volumes()
# Describe volumes to handle filter on volume_ids
volumes = [v for v in volumes if v.id in volume_ids] if volume_ids else volumes
# volumes = self.ec2_backend.describe_volumes()
template = self.response_template(DESCRIBE_VOLUMES_RESPONSE) template = self.response_template(DESCRIBE_VOLUMES_RESPONSE)
return template.render(volumes=volumes) return template.render(volumes=volumes)
@ -103,7 +113,7 @@ CREATE_VOLUME_RESPONSE = """<CreateVolumeResponse xmlns="http://ec2.amazonaws.co
<snapshotId/> <snapshotId/>
<availabilityZone>{{ volume.zone.name }}</availabilityZone> <availabilityZone>{{ volume.zone.name }}</availabilityZone>
<status>creating</status> <status>creating</status>
<createTime>2013-10-04T17:38:53.000Z</createTime> <createTime>{{ volume.create_time}}</createTime>
<volumeType>standard</volumeType> <volumeType>standard</volumeType>
</CreateVolumeResponse>""" </CreateVolumeResponse>"""
@ -117,7 +127,7 @@ DESCRIBE_VOLUMES_RESPONSE = """<DescribeVolumesResponse xmlns="http://ec2.amazon
<snapshotId/> <snapshotId/>
<availabilityZone>{{ volume.zone.name }}</availabilityZone> <availabilityZone>{{ volume.zone.name }}</availabilityZone>
<status>{{ volume.status }}</status> <status>{{ volume.status }}</status>
<createTime>2013-10-04T17:38:53.000Z</createTime> <createTime>{{ volume.create_time}}</createTime>
<attachmentSet> <attachmentSet>
{% if volume.attachment %} {% if volume.attachment %}
<item> <item>
@ -125,7 +135,7 @@ DESCRIBE_VOLUMES_RESPONSE = """<DescribeVolumesResponse xmlns="http://ec2.amazon
<instanceId>{{ volume.attachment.instance.id }}</instanceId> <instanceId>{{ volume.attachment.instance.id }}</instanceId>
<device>{{ volume.attachment.device }}</device> <device>{{ volume.attachment.device }}</device>
<status>attached</status> <status>attached</status>
<attachTime>2013-10-04T17:38:53.000Z</attachTime> <attachTime>{{volume.attachment.attach_time}}</attachTime>
<deleteOnTermination>false</deleteOnTermination> <deleteOnTermination>false</deleteOnTermination>
</item> </item>
{% endif %} {% endif %}
@ -157,7 +167,7 @@ ATTACHED_VOLUME_RESPONSE = """<AttachVolumeResponse xmlns="http://ec2.amazonaws.
<instanceId>{{ attachment.instance.id }}</instanceId> <instanceId>{{ attachment.instance.id }}</instanceId>
<device>{{ attachment.device }}</device> <device>{{ attachment.device }}</device>
<status>attaching</status> <status>attaching</status>
<attachTime>2013-10-04T17:38:53.000Z</attachTime> <attachTime>{{attachment.attach_time}}</attachTime>
</AttachVolumeResponse>""" </AttachVolumeResponse>"""
DETATCH_VOLUME_RESPONSE = """<DetachVolumeResponse xmlns="http://ec2.amazonaws.com/doc/2012-12-01/"> DETATCH_VOLUME_RESPONSE = """<DetachVolumeResponse xmlns="http://ec2.amazonaws.com/doc/2012-12-01/">
@ -174,7 +184,7 @@ CREATE_SNAPSHOT_RESPONSE = """<CreateSnapshotResponse xmlns="http://ec2.amazonaw
<snapshotId>{{ snapshot.id }}</snapshotId> <snapshotId>{{ snapshot.id }}</snapshotId>
<volumeId>{{ snapshot.volume.id }}</volumeId> <volumeId>{{ snapshot.volume.id }}</volumeId>
<status>pending</status> <status>pending</status>
<startTime>2013-10-04T17:38:53.000Z</startTime> <startTime>{{ snapshot.start_time}}</startTime>
<progress>60%</progress> <progress>60%</progress>
<ownerId>111122223333</ownerId> <ownerId>111122223333</ownerId>
<volumeSize>{{ snapshot.volume.size }}</volumeSize> <volumeSize>{{ snapshot.volume.size }}</volumeSize>
@ -189,7 +199,7 @@ DESCRIBE_SNAPSHOTS_RESPONSE = """<DescribeSnapshotsResponse xmlns="http://ec2.am
<snapshotId>{{ snapshot.id }}</snapshotId> <snapshotId>{{ snapshot.id }}</snapshotId>
<volumeId>{{ snapshot.volume.id }}</volumeId> <volumeId>{{ snapshot.volume.id }}</volumeId>
<status>pending</status> <status>pending</status>
<startTime>2013-10-04T17:38:53.000Z</startTime> <startTime>{{ snapshot.start_time}}</startTime>
<progress>30%</progress> <progress>30%</progress>
<ownerId>111122223333</ownerId> <ownerId>111122223333</ownerId>
<volumeSize>{{ snapshot.volume.size }}</volumeSize> <volumeSize>{{ snapshot.volume.size }}</volumeSize>

View File

@ -206,7 +206,7 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<instanceType>{{ instance.instance_type }}</instanceType> <instanceType>{{ instance.instance_type }}</instanceType>
<launchTime>{{ instance.launch_time }}</launchTime> <launchTime>{{ instance.launch_time }}</launchTime>
<placement> <placement>
<availabilityZone>us-east-1b</availabilityZone> <availabilityZone>{{ instance.placement}}</availabilityZone>
<groupName/> <groupName/>
<tenancy>default</tenancy> <tenancy>default</tenancy>
</placement> </placement>
@ -331,7 +331,7 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
<instanceType>{{ instance.instance_type }}</instanceType> <instanceType>{{ instance.instance_type }}</instanceType>
<launchTime>{{ instance.launch_time }}</launchTime> <launchTime>{{ instance.launch_time }}</launchTime>
<placement> <placement>
<availabilityZone>us-west-2a</availabilityZone> <availabilityZone>{{ instance.placement }}</availabilityZone>
<groupName/> <groupName/>
<tenancy>default</tenancy> <tenancy>default</tenancy>
</placement> </placement>
@ -369,15 +369,18 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
<rootDeviceType>ebs</rootDeviceType> <rootDeviceType>ebs</rootDeviceType>
<rootDeviceName>/dev/sda1</rootDeviceName> <rootDeviceName>/dev/sda1</rootDeviceName>
<blockDeviceMapping> <blockDeviceMapping>
{% for device_name,deviceobject in instance.get_block_device_mapping %}
<item> <item>
<deviceName>/dev/sda1</deviceName> <deviceName>{{ device_name }}</deviceName>
<ebs> <ebs>
<volumeId>{{ instance.block_device_mapping['/dev/sda1'].volume_id }}</volumeId> <volumeId>{{ deviceobject.volume_id }}</volumeId>
<status>attached</status> <status>{{ deviceobject.status }}</status>
<attachTime>2015-01-01T00:00:00.000Z</attachTime> <attachTime>{{ deviceobject.attach_time }}</attachTime>
<deleteOnTermination>true</deleteOnTermination> <deleteOnTermination>{{ deviceobject.delete_on_termination }}</deleteOnTermination>
<size>{{deviceobject.size}}</size>
</ebs> </ebs>
</item> </item>
{% endfor %}
</blockDeviceMapping> </blockDeviceMapping>
<virtualizationType>{{ instance.virtualization_type }}</virtualizationType> <virtualizationType>{{ instance.virtualization_type }}</virtualizationType>
<clientToken>ABCDE1234567890123</clientToken> <clientToken>ABCDE1234567890123</clientToken>
@ -547,7 +550,7 @@ EC2_INSTANCE_STATUS = """<?xml version="1.0" encoding="UTF-8"?>
{% for instance in instances %} {% for instance in instances %}
<item> <item>
<instanceId>{{ instance.id }}</instanceId> <instanceId>{{ instance.id }}</instanceId>
<availabilityZone>us-east-1d</availabilityZone> <availabilityZone>{{ instance.placement }}</availabilityZone>
<instanceState> <instanceState>
<code>{{ instance.state_code }}</code> <code>{{ instance.state_code }}</code>
<name>{{ instance.state }}</name> <name>{{ instance.state }}</name>

View File

@ -33,6 +33,19 @@ def test_create_and_delete_volume():
cm.exception.status.should.equal(400) cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_ec2
def test_filter_volume_by_id():
conn = boto.connect_ec2('the_key', 'the_secret')
volume1 = conn.create_volume(80, "us-east-1a")
volume2 = conn.create_volume(36, "us-east-1b")
volume3 = conn.create_volume(20, "us-east-1c")
vol1 = conn.get_all_volumes(volume_ids=volume3.id)
vol1.should.have.length_of(1)
vol1[0].size.should.equal(20)
vol1[0].zone.should.equal('us-east-1c')
vol2 = conn.get_all_volumes(volume_ids=[volume1.id, volume2.id])
vol2.should.have.length_of(2)
@mock_ec2 @mock_ec2
def test_volume_attach_and_detach(): def test_volume_attach_and_detach():
@ -85,6 +98,7 @@ def test_create_snapshot():
snapshots = conn.get_all_snapshots() snapshots = conn.get_all_snapshots()
snapshots.should.have.length_of(1) snapshots.should.have.length_of(1)
snapshots[0].description.should.equal('a test snapshot') snapshots[0].description.should.equal('a test snapshot')
snapshots[0].start_time.should_not.be.none
# Create snapshot without description # Create snapshot without description
snapshot = volume.create_snapshot() snapshot = volume.create_snapshot()
@ -100,6 +114,25 @@ def test_create_snapshot():
cm.exception.status.should.equal(400) cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_ec2
def test_filter_snapshot_by_id():
conn = boto.connect_ec2('the_key', 'the_secret')
volume1 = conn.create_volume(36, "us-east-1a")
snap1 = volume1.create_snapshot('a test snapshot 1')
volume2 = conn.create_volume(42, 'us-east-1a')
snap2 = volume2.create_snapshot('a test snapshot 2')
volume3 = conn.create_volume(84, 'us-east-1a')
snap3 = volume3.create_snapshot('a test snapshot 3')
snapshots1 = conn.get_all_snapshots(snapshot_ids=snap2.id)
snapshots1.should.have.length_of(1)
snapshots1[0].volume_id.should.equal(volume2.id)
snapshots1[0].region.name.should.equal('us-east-1')
snapshots2 = conn.get_all_snapshots(snapshot_ids=[snap2.id, snap3.id])
snapshots2.should.have.length_of(2)
for s in snapshots2:
s.start_time.should_not.be.none
s.volume_id.should.be.within([volume2.id, volume3.id])
s.region.name.should.equal('us-east-1')
@mock_ec2 @mock_ec2
def test_snapshot_attribute(): def test_snapshot_attribute():

View File

@ -53,7 +53,7 @@ def test_instance_launch_and_terminate():
instances.should.have.length_of(1) instances.should.have.length_of(1)
instances[0].id.should.equal(instance.id) instances[0].id.should.equal(instance.id)
instances[0].state.should.equal('running') instances[0].state.should.equal('running')
instances[0].launch_time.should.equal("2014-01-01T05:00:00") instances[0].launch_time.should.equal("2014-01-01T05:00:00Z")
instances[0].vpc_id.should.equal(None) instances[0].vpc_id.should.equal(None)
root_device_name = instances[0].root_device_name root_device_name = instances[0].root_device_name
@ -66,6 +66,35 @@ def test_instance_launch_and_terminate():
instance = reservations[0].instances[0] instance = reservations[0].instances[0]
instance.state.should.equal('terminated') instance.state.should.equal('terminated')
@freeze_time("2014-01-01 05:00:00")
@mock_ec2
def test_instance_attach_volume():
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
vol1 = conn.create_volume(size=36, zone=conn.region.name)
vol1.attach(instance.id, "/dev/sda1")
vol1.update()
vol2 = conn.create_volume(size=65, zone=conn.region.name)
vol2.attach(instance.id, "/dev/sdb1")
vol2.update()
vol3 = conn.create_volume(size=130, zone=conn.region.name)
vol3.attach(instance.id, "/dev/sdc1")
vol3.update()
reservations = conn.get_all_instances()
instance = reservations[0].instances[0]
instance.block_device_mapping.should.have.length_of(3)
for v in conn.get_all_volumes(volume_ids=[instance.block_device_mapping['/dev/sdc1'].volume_id]):
v.attach_data.instance_id.should.equal(instance.id)
v.attach_data.attach_time.should.equal(instance.launch_time) # can do due to freeze_time decorator.
v.create_time.should.equal(instance.launch_time) # can do due to freeze_time decorator.
v.region.name.should.equal(instance.region.name)
v.status.should.equal('in-use')
@mock_ec2 @mock_ec2
def test_get_instances_by_id(): def test_get_instances_by_id():