From 60bba476247830d0d9d1f1b549999bc7f2b190a3 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 17 Jun 2014 19:04:38 +0300 Subject: [PATCH 1/4] switch to using boto's SpotInstanceRequest in backend --- moto/ec2/models.py | 32 ++++++++++-------- moto/ec2/responses/spot_instances.py | 50 ++++++++++++++-------------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 440c1c5a2..208330a40 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -3,6 +3,8 @@ import itertools from collections import defaultdict from boto.ec2.instance import Instance as BotoInstance, Reservation +from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest +from boto.ec2.launchspecification import LaunchSpecification from moto.core import BaseBackend from .exceptions import ( @@ -893,39 +895,41 @@ class VPCGatewayAttachmentBackend(object): return attachment -class SpotInstanceRequest(object): +class SpotInstanceRequest(BotoSpotRequest): 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, - ramdisk_id, monitoring_enabled, subnet_id): + ramdisk_id, monitoring_enabled, subnet_id, **kwargs): + super(SpotInstanceRequest, self).__init__(**kwargs) + ls = LaunchSpecification() + self.launch_specification = ls self.id = spot_request_id self.state = "open" self.price = price - self.image_id = image_id self.type = type self.valid_from = valid_from self.valid_until = valid_until self.launch_group = launch_group self.availability_zone_group = availability_zone_group - self.key_name = key_name - self.user_data = user_data - self.instance_type = instance_type - self.placement = placement - self.kernel_id = kernel_id - self.ramdisk_id = ramdisk_id - self.monitoring_enabled = monitoring_enabled - self.subnet_id = subnet_id + self.user_data = user_data # NOT + ls.kernel = kernel_id + ls.ramdisk = ramdisk_id + ls.image_id = image_id + ls.key_name = key_name + ls.instance_type = instance_type + ls.placement = placement + ls.monitored = monitoring_enabled + ls.subnet_id = subnet_id - self.security_groups = [] if security_groups: for group_name in security_groups: group = ec2_backend.get_security_group_from_name(group_name) if group: - self.security_groups.append(group) + ls.groups.append(group) else: # If not security groups, add the default default_group = ec2_backend.get_security_group_from_name("default") - self.security_groups.append(default_group) + ls.groups.append(default_group) class SpotRequestBackend(object): diff --git a/moto/ec2/responses/spot_instances.py b/moto/ec2/responses/spot_instances.py index 5ce8ee122..c05077407 100644 --- a/moto/ec2/responses/spot_instances.py +++ b/moto/ec2/responses/spot_instances.py @@ -95,27 +95,27 @@ REQUEST_SPOT_INSTANCES_TEMPLATE = """{{ request.availability_zone_group }} {% endif %} - {{ request.image_id }} - {% if request.key_name %} - {{ request.key_name }} + {{ request.launch_specification.image_id }} + {% if request.launch_specification.key_name %} + {{ request.launch_specification.key_name }} {% endif %} - {% for group in request.security_groups %} + {% for group in request.launch_specification.groups %} {{ group.id }} {{ group.name }} {% endfor %} - {% if request.kernel_id %} - {{ request.kernel_id }} + {% if request.launch_specification.kernel %} + {{ request.launch_specification.kernel }} {% endif %} - {% if request.ramdisk_id %} - {{ request.ramdisk_id }} + {% if request.launch_specification.ramdisk %} + {{ request.launch_specification.ramdisk }} {% endif %} - {% if request.subnet_id %} - {{ request.subnet_id }} + {% if request.launch_specification.subnet_id %} + {{ request.launch_specification.subnet_id }} {% endif %} - {{ request.instance_type }} + {{ request.launch_specification.instance_type }} - {{ request.monitoring_enabled }} + {{ request.launch_specification.monitored }} - {{ request.ebs_optimized }} - {% if request.placement %} + {{ request.launch_specification.ebs_optimized }} + {% if request.launch_specification.placement %} - {{ request.placement }} + {{ request.launch_specification.placement }} {% endif %} From 76a6a86eacb36220b495a2ea3f0fe2b28e2ea50a Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Wed, 18 Jun 2014 10:46:20 +0300 Subject: [PATCH 2/4] add Model metaclass to collect model accessor methods from backend classes --- moto/backends.py | 7 +++++++ moto/core/models.py | 22 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/moto/backends.py b/moto/backends.py index e07565497..ed237401a 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -25,3 +25,10 @@ BACKENDS = { 'sts': sts_backend, 'route53': route53_backend } + + +def get_model(name): + for backend in BACKENDS.values(): + models = getattr(backend.__class__, '__models__', {}) + if name in models: + return getattr(backend, models[name])() diff --git a/moto/core/models.py b/moto/core/models.py index c5c23d155..fe0161f8d 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -65,8 +65,28 @@ class MockAWS(object): return wrapper -class BaseBackend(object): +class Model(type): + def __new__(self, clsname, bases, namespace): + cls = super(Model, self).__new__(self, clsname, bases, namespace) + cls.__models__ = {} + for name, value in namespace.iteritems(): + model = getattr(value, "__returns_model__", False) + if model is not False: + cls.__models__[model] = name + for base in bases: + cls.__models__.update(getattr(base, "__models__", {})) + return cls + @staticmethod + def prop(model_name): + """ decorator to mark a class method as returning model values """ + def dec(f): + f.__returns_model__ = model_name + return f + return dec + + +class BaseBackend(object): def reset(self): self.__dict__ = {} self.__init__() From fa8485b59970b68d8206aa880b473b804ef7c8f3 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Wed, 18 Jun 2014 10:46:58 +0300 Subject: [PATCH 3/4] provide SpotRequestBackend with model accessor --- moto/ec2/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 208330a40..b3f4d9a03 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -7,6 +7,7 @@ from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest from boto.ec2.launchspecification import LaunchSpecification from moto.core import BaseBackend +from moto.core.models import Model from .exceptions import ( InvalidIdError, DependencyViolationError, @@ -933,6 +934,8 @@ class SpotInstanceRequest(BotoSpotRequest): class SpotRequestBackend(object): + __metaclass__ = Model + def __init__(self): self.spot_instance_requests = {} super(SpotRequestBackend, self).__init__() @@ -955,6 +958,7 @@ class SpotRequestBackend(object): requests.append(request) return requests + @Model.prop('SpotInstanceRequest') def describe_spot_instance_requests(self): return self.spot_instance_requests.values() From 955bd6feef6de56a25a35ec48b2517257d560518 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 29 Jul 2014 15:29:52 +0300 Subject: [PATCH 4/4] add a test for model accessor --- tests/test_ec2/test_spot_instances.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py index 91a3158eb..46a5d7136 100644 --- a/tests/test_ec2/test_spot_instances.py +++ b/tests/test_ec2/test_spot_instances.py @@ -4,6 +4,7 @@ import boto import sure # noqa from moto import mock_ec2 +from moto.backends import get_model from moto.core.utils import iso_8601_datetime @@ -97,3 +98,29 @@ def test_cancel_spot_instance_request(): requests = conn.get_all_spot_instance_requests() requests.should.have.length_of(0) + + +@mock_ec2 +def test_request_spot_instances_fulfilled(): + """ + Test that moto correctly fullfills a spot instance request + """ + conn = boto.connect_ec2() + + request = conn.request_spot_instances( + price=0.5, image_id='ami-abcd1234', + ) + + requests = conn.get_all_spot_instance_requests() + requests.should.have.length_of(1) + request = requests[0] + + request.state.should.equal("open") + + get_model('SpotInstanceRequest')[0].state = 'active' + + requests = conn.get_all_spot_instance_requests() + requests.should.have.length_of(1) + request = requests[0] + + request.state.should.equal("active")