Add ec2 filtering by instance state

This commit is contained in:
Steve Pulec 2013-07-08 21:18:05 -04:00
parent 76ea9172da
commit 257ca9f771
5 changed files with 98 additions and 4 deletions

4
moto/ec2/exceptions.py Normal file
View File

@ -0,0 +1,4 @@
class InvalidIdError(RuntimeError):
def __init__(self, instance_id):
super(InvalidIdError, self).__init__()
self.instance_id = instance_id

View File

@ -4,6 +4,7 @@ from collections import defaultdict
from boto.ec2.instance import Instance as BotoInstance, Reservation
from moto.core import BaseBackend
from .exceptions import InvalidIdError
from .utils import (
random_ami_id,
random_instance_id,
@ -137,6 +138,10 @@ class InstanceBackend(object):
reservation_copy = copy.deepcopy(reservation)
reservation_copy.instances = [instance for instance in reservation_copy.instances if instance.id in instance_ids]
reservations.append(reservation_copy)
found_instance_ids = [instance.id for reservation in reservations for instance in reservation.instances]
if len(found_instance_ids) != len(instance_ids):
invalid_id = list(set(instance_ids).difference(set(found_instance_ids)))[0]
raise InvalidIdError(invalid_id)
return reservations
def all_reservations(self):

View File

@ -2,17 +2,26 @@ from jinja2 import Template
from moto.core.utils import camelcase_to_underscores
from moto.ec2.models import ec2_backend
from moto.ec2.utils import instance_ids_from_querystring
from moto.ec2.utils import instance_ids_from_querystring, filters_from_querystring, filter_reservations
from moto.ec2.exceptions import InvalidIdError
class InstanceResponse(object):
def describe_instances(self):
instance_ids = instance_ids_from_querystring(self.querystring)
template = Template(EC2_DESCRIBE_INSTANCES)
if instance_ids:
reservations = ec2_backend.get_reservations_by_instance_ids(instance_ids)
try:
reservations = ec2_backend.get_reservations_by_instance_ids(instance_ids)
except InvalidIdError as exc:
template = Template(EC2_INVALID_INSTANCE_ID)
return template.render(instance_id=exc.instance_id), dict(status=400)
else:
reservations = ec2_backend.all_reservations()
filter_dict = filters_from_querystring(self.querystring)
reservations = filter_reservations(reservations, filter_dict)
template = Template(EC2_DESCRIBE_INSTANCES)
return template.render(reservations=reservations)
def run_instances(self):
@ -263,3 +272,10 @@ EC2_MODIFY_INSTANCE_ATTRIBUTE = """<ModifyInstanceAttributeResponse xmlns="http:
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</ModifyInstanceAttributeResponse>"""
EC2_INVALID_INSTANCE_ID = """<?xml version="1.0" encoding="UTF-8"?>
<Response><Errors><Error><Code>InvalidInstanceID.NotFound</Code>
<Message>The instance ID '{{ instance_id }}' does not exist</Message></Error>
</Errors>
<RequestID>39070fe4-6f6d-4565-aecd-7850607e4555</RequestID></Response>"""

View File

@ -1,4 +1,5 @@
import random
import re
def random_id(prefix=''):
@ -53,7 +54,7 @@ def resource_ids_from_querystring(querystring_dict):
prefix = 'ResourceId'
response_values = {}
for key, value in querystring_dict.iteritems():
if prefix in key:
if key.startswith(prefix):
resource_index = key.replace(prefix + ".", "")
tag_key = querystring_dict.get("Tag.{}.Key".format(resource_index))[0]
@ -65,3 +66,42 @@ def resource_ids_from_querystring(querystring_dict):
response_values[value[0]] = (tag_key, tag_value)
return response_values
def filters_from_querystring(querystring_dict):
response_values = {}
for key, value in querystring_dict.iteritems():
match = re.search("Filter.(\d).Name", key)
if match:
filter_index = match.groups()[0]
value_prefix = "Filter.{}.Value".format(filter_index)
filter_values = [filter_value[0] for filter_key, filter_value in querystring_dict.iteritems() if filter_key.startswith(value_prefix)]
response_values[value[0]] = filter_values
return response_values
filter_dict_attribute_mapping = {
'instance-state-name': 'state'
}
def passes_filter_dict(instance, filter_dict):
for filter_name, filter_values in filter_dict.iteritems():
if filter_name in filter_dict_attribute_mapping:
instance_attr = filter_dict_attribute_mapping[filter_name]
else:
raise NotImplementedError("Filter dicts have not been implemented in Moto for '%s' yet. Feel free to open an issue at https://github.com/spulec/moto/issues", filter_name)
instance_value = getattr(instance, instance_attr)
if instance_value not in filter_values:
return False
return True
def filter_reservations(reservations, filter_dict):
for reservation in reservations:
new_instances = []
for instance in reservation.instances:
if passes_filter_dict(instance, filter_dict):
new_instances.append(instance)
reservation.instances = new_instances
return reservations

View File

@ -2,6 +2,7 @@ import base64
import boto
from boto.ec2.instance import Reservation, InstanceAttribute
from boto.exception import EC2ResponseError
import sure # flake8: noqa
from moto import mock_ec2
@ -69,6 +70,34 @@ def test_get_instances_by_id():
instance_ids = [instance.id for instance in reservation.instances]
instance_ids.should.equal([instance1.id, instance2.id])
# Call get_all_instances with a bad id should raise an error
conn.get_all_instances.when.called_with(instance_ids=[instance1.id, "i-1234abcd"]).should.throw(
EC2ResponseError,
"The instance ID 'i-1234abcd' does not exist"
)
@mock_ec2
def test_get_instances_filtering_by_state():
conn = boto.connect_ec2()
reservation = conn.run_instances('ami-1234abcd', min_count=3)
instance1, instance2, instance3 = reservation.instances
conn.terminate_instances([instance1.id])
reservations = conn.get_all_instances(filters={'instance-state-name': 'pending'})
reservations.should.have.length_of(1)
# Since we terminated instance1, only instance2 and instance3 should be returned
instance_ids = [instance.id for instance in reservations[0].instances]
set(instance_ids).should.equal(set([instance2.id, instance3.id]))
reservations = conn.get_all_instances([instance2.id], filters={'instance-state-name': 'pending'})
reservations.should.have.length_of(1)
instance_ids = [instance.id for instance in reservations[0].instances]
instance_ids.should.equal([instance2.id])
conn.get_all_instances.when.called_with(filters={'not-implemented-filter': 'foobar'}).should.throw(NotImplementedError)
@mock_ec2
def test_instance_start_and_stop():