From 2bb79824ce2a687bd602e077b371738cc6453e16 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Sun, 26 Jul 2015 09:37:20 +1000 Subject: [PATCH] Volume attachments to show in instance. Volumes and Snapshots to be searchable by their id Placement of instance to match region connection Times for creation and attachment to show based on api call --- moto/ec2/models.py | 25 +++++++++++++++++++++-- moto/ec2/responses/elastic_block_store.py | 22 ++++++++++++++------ moto/ec2/responses/instances.py | 19 +++++++++-------- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 3f3fc4491..19d005867 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from collections import defaultdict import copy from datetime import datetime + import itertools import re @@ -97,6 +98,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): for resource_id in resource_ids: if not is_valid_resource_id(resource_id): @@ -309,14 +313,17 @@ class Instance(BotoInstance, TaggedEC2Resource): in_ec2_classic = not bool(self.subnet_id) self.key_name = kwargs.get("key_name") 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) if in_ec2_classic: # If we are in EC2-Classic, autoassign a public IP associate_public_ip = True 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}) ami = amis[0] if amis else None @@ -343,6 +350,10 @@ class Instance(BotoInstance, TaggedEC2Resource): private_ip=kwargs.get("private_ip"), associate_public_ip=associate_public_ip) + @property + def get_block_device_mapping(self): + return self.block_device_mapping.items() + @property def private_ip(self): return self.nics[0].private_ip_address @@ -1349,6 +1360,7 @@ class SecurityGroupIngress(object): class VolumeAttachment(object): def __init__(self, volume, instance, device): self.volume = volume + self.attach_time = utc_date_and_time() self.instance = instance self.device = device @@ -1373,6 +1385,7 @@ class Volume(TaggedEC2Resource): self.id = volume_id self.size = size self.zone = zone + self.create_time = utc_date_and_time() self.attachment = None self.ec2_backend = ec2_backend @@ -1404,6 +1417,7 @@ class Snapshot(TaggedEC2Resource): self.id = snapshot_id self.volume = volume self.description = description + self.start_time = utc_date_and_time() self.create_volume_permission_groups = set() self.ec2_backend = ec2_backend @@ -1444,6 +1458,13 @@ class EBSBackend(object): return False 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 def detach_volume(self, volume_id, instance_id, device_path): diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index 96586a9bb..abb371260 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -42,12 +42,22 @@ class ElasticBlockStore(BaseResponse): return DELETE_VOLUME_RESPONSE 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() + # 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) return template.render(snapshots=snapshots) 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() + # 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) return template.render(volumes=volumes) @@ -103,7 +113,7 @@ CREATE_VOLUME_RESPONSE = """ @@ -174,7 +184,7 @@ CREATE_SNAPSHOT_RESPONSE = """ebs /dev/sda1 + {% for device_name,deviceobject in instance.get_block_device_mapping %} - /dev/sda1 + {{ device_name }} - {{ instance.block_device_mapping['/dev/sda1'].volume_id }} - attached - 2015-01-01T00:00:00.000Z - true + {{ deviceobject.volume_id }} + {{ deviceobject.status }} + {{ deviceobject.attach_time }} + {{ deviceobject.delete_on_termination }} + {{deviceobject.size}} + {% endfor %} {{ instance.virtualization_type }} ABCDE1234567890123 @@ -547,7 +550,7 @@ EC2_INSTANCE_STATUS = """ {% for instance in instances %} {{ instance.id }} - us-east-1d + {{ instance.placement }} {{ instance.state_code }} {{ instance.state }}