Add ec2 filtering by instance state
This commit is contained in:
parent
76ea9172da
commit
257ca9f771
4
moto/ec2/exceptions.py
Normal file
4
moto/ec2/exceptions.py
Normal file
@ -0,0 +1,4 @@
|
||||
class InvalidIdError(RuntimeError):
|
||||
def __init__(self, instance_id):
|
||||
super(InvalidIdError, self).__init__()
|
||||
self.instance_id = instance_id
|
@ -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):
|
||||
|
@ -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>"""
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user