diff --git a/moto/ec2/models.py b/moto/ec2/models.py index e78137ca..4bec04af 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import copy import itertools from collections import defaultdict +from datetime import datetime import six import boto @@ -96,6 +97,11 @@ class InstanceState(object): self.name = name self.code = code +class StateReason(object): + def __init__(self, message="", code=""): + self.message = message + self.code = code + class TaggedEC2Resource(object): def get_tags(self, *args, **kwargs): @@ -259,6 +265,8 @@ class Instance(BotoInstance, TaggedEC2Resource): self.id = random_instance_id() self.image_id = image_id self._state = InstanceState("running", 16) + self._reason = "" + self._state_reason = StateReason() self.user_data = user_data self.security_groups = security_groups self.instance_type = kwargs.get("instance_type", "m1.small") @@ -318,6 +326,9 @@ class Instance(BotoInstance, TaggedEC2Resource): self._state.name = "running" self._state.code = 16 + self._reason = "" + self._state_reason = StateReason() + def stop(self, *args, **kwargs): for nic in self.nics.values(): nic.stop() @@ -325,6 +336,10 @@ class Instance(BotoInstance, TaggedEC2Resource): self._state.name = "stopped" self._state.code = 80 + self._reason = "User initiated ({0})".format(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')) + self._state_reason = StateReason("Client.UserInitiatedShutdown: User initiated shutdown", + "Client.UserInitiatedShutdown") + def terminate(self, *args, **kwargs): for nic in self.nics.values(): nic.stop() @@ -332,10 +347,17 @@ class Instance(BotoInstance, TaggedEC2Resource): self._state.name = "terminated" self._state.code = 48 + self._reason = "User initiated ({0})".format(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')) + self._state_reason = StateReason("Client.UserInitiatedShutdown: User initiated shutdown", + "Client.UserInitiatedShutdown") + def reboot(self, *args, **kwargs): self._state.name = "running" self._state.code = 16 + self._reason = "" + self._state_reason = StateReason() + def get_tags(self): tags = ec2_backend.describe_tags(filters={'resource-id': [self.id]}) return tags diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index b1833ee2..70f19a8f 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -303,7 +303,7 @@ EC2_DESCRIBE_INSTANCES = """ {% endfor %} + + {{ instance._state_reason.code }} + {{ instance._state_reason.message }} + {{ instance.architecture }} {{ instance.kernel }} ebs diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index e16a6935..7251ff0b 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -271,15 +271,27 @@ def keypair_names_from_querystring(querystring_dict): filter_dict_attribute_mapping = { 'instance-state-name': 'state', - 'instance-id': 'id' + 'instance-id': 'id', + 'state-reason-code': '_state_reason.code', } +def get_instance_value(instance, instance_attr): + keys = instance_attr.split('.') + val = instance + for key in keys: + if hasattr(val, key): + val = getattr(val, key) + elif isinstance(val, dict): + val = val[key] + else: + return None + return val def passes_filter_dict(instance, filter_dict): for filter_name, filter_values in filter_dict.items(): if filter_name in filter_dict_attribute_mapping: instance_attr = filter_dict_attribute_mapping[filter_name] - instance_value = getattr(instance, instance_attr) + instance_value = get_instance_value(instance, instance_attr) if instance_value not in filter_values: return False elif filter_name.startswith('tag:'): diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index bc6938e8..955ce047 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -135,6 +135,24 @@ def test_get_instances_filtering_by_instance_id(): reservations = conn.get_all_instances(filters={'instance-id': 'non-existing-id'}) reservations.should.have.length_of(0) +@mock_ec2 +def test_get_instances_filtering_by_reason_code(): + conn = boto.connect_ec2() + reservation = conn.run_instances('ami-1234abcd', min_count=3) + instance1, instance2, instance3 = reservation.instances + instance1.stop() + instance2.terminate() + + reservations = conn.get_all_instances(filters={'state-reason-code': 'Client.UserInitiatedShutdown'}) + # get_all_instances should return instance1 and instance2 + reservations[0].instances.should.have.length_of(2) + set([instance1.id, instance2.id]).should.equal(set([i.id for i in reservations[0].instances])) + + reservations = conn.get_all_instances(filters={'state-reason-code': ''}) + # get_all_instances should return instance 3 + reservations[0].instances.should.have.length_of(1) + reservations[0].instances[0].id.should.equal(instance3.id) + @mock_ec2 def test_get_instances_filtering_by_tag(): conn = boto.connect_ec2()