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__() diff --git a/moto/ec2/models.py b/moto/ec2/models.py index c25f49e28..3e384288d 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -3,8 +3,11 @@ 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 moto.core.models import Model from .exceptions import ( InvalidIdError, DependencyViolationError, @@ -937,42 +940,46 @@ 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): + __metaclass__ = Model + def __init__(self): self.spot_instance_requests = {} super(SpotRequestBackend, self).__init__() @@ -995,6 +1002,7 @@ class SpotRequestBackend(object): requests.append(request) return requests + @Model.prop('SpotInstanceRequest') def describe_spot_instance_requests(self): return self.spot_instance_requests.values() 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 %} 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")