diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 540d98744..86675a4b1 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1260,7 +1260,7 @@ class VPCGatewayAttachmentBackend(object): return attachment -class SpotInstanceRequest(BotoSpotRequest): +class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Instance): def __init__(self, spot_request_id, price, image_id, type, valid_from, valid_until, launch_group, availability_zone_group, key_name, security_groups, user_data, instance_type, placement, kernel_id, @@ -1296,6 +1296,16 @@ class SpotInstanceRequest(BotoSpotRequest): default_group = ec2_backend.get_security_group_from_name("default") ls.groups.append(default_group) + def get_filter_value(self, filter_name): + if filter_name == 'state': + return self.state + elif filter_name.startswith('tag:'): + tag_name = filter_name.replace('tag:', '', 1) + tags = dict((tag['key'], tag['value']) for tag in self.get_tags()) + return tags.get(tag_name) + else: + ec2_backend.raise_not_implemented_error("The filter '{0}' for DescribeSpotInstanceRequests".format(filter_name)) + @six.add_metaclass(Model) class SpotRequestBackend(object): @@ -1322,8 +1332,14 @@ class SpotRequestBackend(object): return requests @Model.prop('SpotInstanceRequest') - def describe_spot_instance_requests(self): - return self.spot_instance_requests.values() + def describe_spot_instance_requests(self, filters=None): + requests = self.spot_instance_requests.values() + + if filters: + for (_filter, _filter_value) in filters.items(): + requests = [ request for request in requests if request.get_filter_value(_filter) in _filter_value ] + + return requests def cancel_spot_instance_requests(self, request_ids): requests = [] diff --git a/moto/ec2/responses/spot_instances.py b/moto/ec2/responses/spot_instances.py index fd848093c..0cc632909 100644 --- a/moto/ec2/responses/spot_instances.py +++ b/moto/ec2/responses/spot_instances.py @@ -3,6 +3,7 @@ from jinja2 import Template from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend +from moto.ec2.utils import filters_from_querystring class SpotInstances(BaseResponse): @@ -30,7 +31,8 @@ class SpotInstances(BaseResponse): raise NotImplementedError('SpotInstances.describe_spot_datafeed_subscription is not yet implemented') def describe_spot_instance_requests(self): - requests = ec2_backend.describe_spot_instance_requests() + filters = filters_from_querystring(self.querystring) + requests = ec2_backend.describe_spot_instance_requests(filters=filters) template = Template(DESCRIBE_SPOT_INSTANCES_TEMPLATE) return template.render(requests=requests) @@ -186,6 +188,16 @@ DESCRIBE_SPOT_INSTANCES_TEMPLATE = """ {% endif %} + + {% for tag in request.get_tags() %} + + {{ tag.resource_id }} + {{ tag.resource_type }} + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + {% if request.launch_group %} {{ request.launch_group }} {% endif %} diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py index a79b70e0a..26676780b 100644 --- a/tests/test_ec2/test_spot_instances.py +++ b/tests/test_ec2/test_spot_instances.py @@ -125,3 +125,58 @@ def test_request_spot_instances_fulfilled(): request = requests[0] request.state.should.equal("active") + + +@mock_ec2 +def test_tag_spot_instance_request(): + """ + Test that moto correctly tags a spot instance request + """ + conn = boto.connect_ec2() + + request = conn.request_spot_instances( + price=0.5, image_id='ami-abcd1234', + ) + request[0].add_tag('tag1', 'value1') + request[0].add_tag('tag2', 'value2') + + requests = conn.get_all_spot_instance_requests() + requests.should.have.length_of(1) + request = requests[0] + + tag_dict = dict(request.tags) + tag_dict.should.equal({'tag1' : 'value1', 'tag2' : 'value2'}) + + +@mock_ec2 +def test_get_all_spot_instance_requests_filtering(): + """ + Test that moto correctly filters spot instance requests + """ + conn = boto.connect_ec2() + + request1 = conn.request_spot_instances( + price=0.5, image_id='ami-abcd1234', + ) + request2 = conn.request_spot_instances( + price=0.5, image_id='ami-abcd1234', + ) + request3 = conn.request_spot_instances( + price=0.5, image_id='ami-abcd1234', + ) + request1[0].add_tag('tag1', 'value1') + request1[0].add_tag('tag2', 'value2') + request2[0].add_tag('tag1', 'value1') + request2[0].add_tag('tag2', 'wrong') + + requests = conn.get_all_spot_instance_requests(filters={'state' : 'active'}) + requests.should.have.length_of(0) + + requests = conn.get_all_spot_instance_requests(filters={'state' : 'open'}) + requests.should.have.length_of(3) + + requests = conn.get_all_spot_instance_requests(filters={'tag:tag1' : 'value1'}) + requests.should.have.length_of(2) + + requests = conn.get_all_spot_instance_requests(filters={'tag:tag1' : 'value1', 'tag:tag2' : 'value2'}) + requests.should.have.length_of(1)