From 6963866c7eece36ee9ce049ee7548994b1399971 Mon Sep 17 00:00:00 2001 From: Arthur Wang Date: Mon, 20 Oct 2014 15:54:00 -0400 Subject: [PATCH 1/2] Add ec2 instance state reason - Add instance.reason and instance.state_reason (http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-Item Type-StateReasonType.html) - Add ec2 filtering by state-reason-code and state-reason-message --- moto/ec2/models.py | 22 ++++++++++++++++++++++ moto/ec2/responses/instances.py | 6 +++++- moto/ec2/utils.py | 17 +++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 32b6b1660..0540abc1c 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 @@ -95,6 +96,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): @@ -258,6 +264,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") @@ -317,6 +325,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() @@ -324,6 +335,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() @@ -331,10 +346,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 b1833ee27..70f19a8f2 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 1a590d6d7..06ff19413 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -266,15 +266,28 @@ 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', + 'state-reason-message': '_state_reason.message' } +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:'): From c0049578cbdbd9f14c82374e697e2d35039e0821 Mon Sep 17 00:00:00 2001 From: Arthur Wang Date: Mon, 20 Oct 2014 21:00:33 +0000 Subject: [PATCH 2/2] Test filtering get_instances by reason code --- moto/ec2/utils.py | 1 - tests/test_ec2/test_instances.py | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 06ff19413..6ab92283b 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -268,7 +268,6 @@ filter_dict_attribute_mapping = { 'instance-state-name': 'state', 'instance-id': 'id', 'state-reason-code': '_state_reason.code', - 'state-reason-message': '_state_reason.message' } def get_instance_value(instance, instance_attr): diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index bc6938e82..955ce0477 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()