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 boto.ec2.instance import Instance as BotoInstance, Reservation
from moto.core import BaseBackend from moto.core import BaseBackend
from .exceptions import InvalidIdError
from .utils import ( from .utils import (
random_ami_id, random_ami_id,
random_instance_id, random_instance_id,
@ -137,6 +138,10 @@ class InstanceBackend(object):
reservation_copy = copy.deepcopy(reservation) reservation_copy = copy.deepcopy(reservation)
reservation_copy.instances = [instance for instance in reservation_copy.instances if instance.id in instance_ids] reservation_copy.instances = [instance for instance in reservation_copy.instances if instance.id in instance_ids]
reservations.append(reservation_copy) 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 return reservations
def all_reservations(self): def all_reservations(self):

View File

@ -2,17 +2,26 @@ from jinja2 import Template
from moto.core.utils import camelcase_to_underscores from moto.core.utils import camelcase_to_underscores
from moto.ec2.models import ec2_backend 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): class InstanceResponse(object):
def describe_instances(self): def describe_instances(self):
instance_ids = instance_ids_from_querystring(self.querystring) instance_ids = instance_ids_from_querystring(self.querystring)
template = Template(EC2_DESCRIBE_INSTANCES)
if instance_ids: 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: else:
reservations = ec2_backend.all_reservations() 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) return template.render(reservations=reservations)
def run_instances(self): def run_instances(self):
@ -263,3 +272,10 @@ EC2_MODIFY_INSTANCE_ATTRIBUTE = """<ModifyInstanceAttributeResponse xmlns="http:
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return> <return>true</return>
</ModifyInstanceAttributeResponse>""" </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 random
import re
def random_id(prefix=''): def random_id(prefix=''):
@ -53,7 +54,7 @@ def resource_ids_from_querystring(querystring_dict):
prefix = 'ResourceId' prefix = 'ResourceId'
response_values = {} response_values = {}
for key, value in querystring_dict.iteritems(): for key, value in querystring_dict.iteritems():
if prefix in key: if key.startswith(prefix):
resource_index = key.replace(prefix + ".", "") resource_index = key.replace(prefix + ".", "")
tag_key = querystring_dict.get("Tag.{}.Key".format(resource_index))[0] 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) response_values[value[0]] = (tag_key, tag_value)
return response_values 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 import boto
from boto.ec2.instance import Reservation, InstanceAttribute from boto.ec2.instance import Reservation, InstanceAttribute
from boto.exception import EC2ResponseError
import sure # flake8: noqa import sure # flake8: noqa
from moto import mock_ec2 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 = [instance.id for instance in reservation.instances]
instance_ids.should.equal([instance1.id, instance2.id]) 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 @mock_ec2
def test_instance_start_and_stop(): def test_instance_start_and_stop():