From 53fdf330ee05289879d427d65cf368fdda47d292 Mon Sep 17 00:00:00 2001 From: Peter Van Bouwel Date: Sat, 8 Nov 2014 12:12:20 +0100 Subject: [PATCH 01/21] Tests are added that verify that when a tag is being set on an (EBS) volume or on an instance that upon retrieval from the resource, the tag are set on the instance. Important is that the tags are set using create_tags but that the presence is validated by getting the resource using either (get_all_volumes or get_all_instances). When running the tests using nosetests it shows that the tags for the instances are correctly retrieved but the tags for the volume are missing --- tests/test_ec2/test_tags.py | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index 69bae7410..1dcb75d34 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -3,6 +3,7 @@ import itertools import boto from boto.exception import EC2ResponseError +from boto.ec2.instance import Reservation import sure # noqa from moto import mock_ec2 @@ -253,3 +254,57 @@ def test_get_all_tags_value_filter(): tags = conn.get_all_tags(filters={'value': '*value\*\?'}) tags.should.have.length_of(1) + + +@mock_ec2 +def test_create_tags_must_set_tags_on_retrieved_instances(): + tag_key = 'Tag name' + tag_value = 'Tag value' + tags_to_be_set = {tag_key: tag_value} + + conn = boto.connect_ec2('the_key', 'the_secret') + reservation = conn.run_instances('ami-1234abcd') + reservation.should.be.a(Reservation) + reservation.instances.should.have.length_of(1) + instance = reservation.instances[0] + + reservations = conn.get_all_instances() + reservations.should.have.length_of(1) + reservations[0].id.should.equal(reservation.id) + instances = reservations[0].instances + instances.should.have.length_of(1) + instances[0].id.should.equal(instance.id) + + conn.create_tags([instance.id], tags_to_be_set) + reservations = conn.get_all_instances() + instance = reservations[0].instances[0] + retrieved_tags = instance.tags + + #Cleanup of instance + conn.terminate_instances([instances[0].id]) + + #Check whether tag is present with correct value + retrieved_tags[tag_key].should.equal(tag_value) + + +@mock_ec2 +def test_create_tags_must_set_tags_on_retrieved_volumes(): + tag_key = 'Tag name' + tag_value = 'Tag value' + tags_to_be_set = {tag_key: tag_value} + conn = boto.connect_ec2('the_key', 'the_secret') + volume = conn.create_volume(80, "us-east-1a") + + all_volumes = conn.get_all_volumes() + volume = all_volumes[0] + conn.create_tags([volume.id], tags_to_be_set) + + #Fetch the volume again + all_volumes = conn.get_all_volumes() + volume = all_volumes[0] + retrieved_tags = volume.tags + + volume.delete() + + #Check whether tag is present with correct value + retrieved_tags[tag_key].should.equal(tag_value) \ No newline at end of file From 17356fe56c0502de0b9cdee2e54ecf62f806b76f Mon Sep 17 00:00:00 2001 From: Peter Van Bouwel Date: Sun, 9 Nov 2014 12:21:19 +0100 Subject: [PATCH 02/21] Extend the DESCRIBE_VOLUMES_RESPONSE to include the tagSet as documented by AWS on http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-ItemType-DescribeVolumesSetItemResponseType.html . This is needed to pass the test that was added in previous commit. --- moto/ec2/models.py | 4 ++++ moto/ec2/responses/elastic_block_store.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 377699164..df89be4ce 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1255,6 +1255,10 @@ class Volume(object): else: return 'available' + def get_tags(self): + tags = ec2_backend.describe_tags(filters={'resource-id': [self.id]}) + return tags + class Snapshot(object): def __init__(self, snapshot_id, volume, description): diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index 868cf2215..93009294b 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -132,6 +132,16 @@ DESCRIBE_VOLUMES_RESPONSE = """ Date: Tue, 11 Nov 2014 10:26:02 +0100 Subject: [PATCH 04/21] Alter get_tags to use the backend associated with the taggable object. Also give volume and snapshot an additional backend attribute. --- moto/ec2/models.py | 20 +++++++++++++++----- tests/test_ec2/test_tags.py | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 4684e02b3..8f9220142 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -109,7 +109,15 @@ class StateReason(object): class TaggedEC2Resource(object): def get_tags(self, *args, **kwargs): - tags = ec2_backend.describe_tags(filters={'resource-id': [self.id]}) + if hasattr(self,"ec2_backend"): + backend = self.ec2_backend + elif hasattr(self,"ebs_backend"): + backend = self.ebs_backend + else: + raise NotImplementedError("Tagging of an object with backend that differs from ec2_backend or ebs_backend.") + + + tags = backend.describe_tags(filters={'resource-id': [self.id]}) return tags def get_filter_value(self, filter_name): @@ -1224,11 +1232,12 @@ class VolumeAttachment(object): class Volume(TaggedEC2Resource): - def __init__(self, volume_id, size, zone): + def __init__(self, ebs_backend, volume_id, size, zone): self.id = volume_id self.size = size self.zone = zone self.attachment = None + self.ebs_backend = ebs_backend @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -1253,11 +1262,12 @@ class Volume(TaggedEC2Resource): class Snapshot(TaggedEC2Resource): - def __init__(self, snapshot_id, volume, description): + def __init__(self, ebs_backend, snapshot_id, volume, description): self.id = snapshot_id self.volume = volume self.description = description self.create_volume_permission_groups = set() + self.ebs_backend = ebs_backend class EBSBackend(object): @@ -1270,7 +1280,7 @@ class EBSBackend(object): def create_volume(self, size, zone_name): volume_id = random_volume_id() zone = self.get_zone_by_name(zone_name) - volume = Volume(volume_id, size, zone) + volume = Volume(self, volume_id, size, zone) self.volumes[volume_id] = volume return volume @@ -1312,7 +1322,7 @@ class EBSBackend(object): def create_snapshot(self, volume_id, description): snapshot_id = random_snapshot_id() volume = self.get_volume(volume_id) - snapshot = Snapshot(snapshot_id, volume, description) + snapshot = Snapshot(self, snapshot_id, volume, description) self.snapshots[snapshot_id] = snapshot return snapshot diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index 15717519d..72e72fb86 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -315,8 +315,8 @@ def test_retrieved_snapshots_must_contain_their_tags(): tag_key = 'Tag name' tag_value = 'Tag value' tags_to_be_set = {tag_key: tag_value} - conn = boto.connect_ec2('the_key', 'the_secret') - volume = conn.create_volume(80, "us-east-1a") + conn = boto.connect_ec2(aws_access_key_id='the_key', aws_secret_access_key='the_secret') + volume = conn.create_volume(80, "eu-west-1a") snapshot = conn.create_snapshot(volume.id) conn.create_tags([snapshot.id], tags_to_be_set) From 63c7e224a2f6a1e0dfe9fa1d4dbf61a755ee2bee Mon Sep 17 00:00:00 2001 From: Peter Van Bouwel Date: Tue, 11 Nov 2014 19:41:16 +0100 Subject: [PATCH 05/21] Always use ec2_backend to get the tag information in order to have a cleaner get_tags method. --- moto/ec2/models.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 8f9220142..05650904a 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -109,15 +109,7 @@ class StateReason(object): class TaggedEC2Resource(object): def get_tags(self, *args, **kwargs): - if hasattr(self,"ec2_backend"): - backend = self.ec2_backend - elif hasattr(self,"ebs_backend"): - backend = self.ebs_backend - else: - raise NotImplementedError("Tagging of an object with backend that differs from ec2_backend or ebs_backend.") - - - tags = backend.describe_tags(filters={'resource-id': [self.id]}) + tags = self.ec2_backend.describe_tags(filters={'resource-id': [self.id]}) return tags def get_filter_value(self, filter_name): @@ -1232,12 +1224,12 @@ class VolumeAttachment(object): class Volume(TaggedEC2Resource): - def __init__(self, ebs_backend, volume_id, size, zone): + def __init__(self, volume_id, size, zone): self.id = volume_id self.size = size self.zone = zone self.attachment = None - self.ebs_backend = ebs_backend + self.ec2_backend = ec2_backend @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -1262,12 +1254,12 @@ class Volume(TaggedEC2Resource): class Snapshot(TaggedEC2Resource): - def __init__(self, ebs_backend, snapshot_id, volume, description): + def __init__(self, snapshot_id, volume, description): self.id = snapshot_id self.volume = volume self.description = description self.create_volume_permission_groups = set() - self.ebs_backend = ebs_backend + self.ec2_backend = ec2_backend class EBSBackend(object): @@ -1280,7 +1272,7 @@ class EBSBackend(object): def create_volume(self, size, zone_name): volume_id = random_volume_id() zone = self.get_zone_by_name(zone_name) - volume = Volume(self, volume_id, size, zone) + volume = Volume(volume_id, size, zone) self.volumes[volume_id] = volume return volume @@ -1322,7 +1314,7 @@ class EBSBackend(object): def create_snapshot(self, volume_id, description): snapshot_id = random_snapshot_id() volume = self.get_volume(volume_id) - snapshot = Snapshot(self, snapshot_id, volume, description) + snapshot = Snapshot(snapshot_id, volume, description) self.snapshots[snapshot_id] = snapshot return snapshot From 6f3506e81068c28c8dd41598267249adbe0988d5 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 08:46:02 -0500 Subject: [PATCH 06/21] Stop testing old boto versions. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c52276b88..57c8270f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ env: matrix: - BOTO_VERSION=2.34.0 - BOTO_VERSION=2.25.0 - - BOTO_VERSION=2.7 matrix: include: - python: "3.3" From d734bca6a57ed18a6070a1a606545b02beba2a74 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 09:17:35 -0500 Subject: [PATCH 07/21] Fix for Volumes and Snapshots to use correct region. --- moto/ec2/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 05650904a..309cab6bf 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1224,7 +1224,7 @@ class VolumeAttachment(object): class Volume(TaggedEC2Resource): - def __init__(self, volume_id, size, zone): + def __init__(self, ec2_backend, volume_id, size, zone): self.id = volume_id self.size = size self.zone = zone @@ -1254,7 +1254,7 @@ class Volume(TaggedEC2Resource): class Snapshot(TaggedEC2Resource): - def __init__(self, snapshot_id, volume, description): + def __init__(self, ec2_backend, snapshot_id, volume, description): self.id = snapshot_id self.volume = volume self.description = description @@ -1272,7 +1272,7 @@ class EBSBackend(object): def create_volume(self, size, zone_name): volume_id = random_volume_id() zone = self.get_zone_by_name(zone_name) - volume = Volume(volume_id, size, zone) + volume = Volume(self, volume_id, size, zone) self.volumes[volume_id] = volume return volume @@ -1314,7 +1314,7 @@ class EBSBackend(object): def create_snapshot(self, volume_id, description): snapshot_id = random_snapshot_id() volume = self.get_volume(volume_id) - snapshot = Snapshot(snapshot_id, volume, description) + snapshot = Snapshot(self, snapshot_id, volume, description) self.snapshots[snapshot_id] = snapshot return snapshot From fd26441e44e8a5bd69e73694e1fcc39eef290b91 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 09:18:39 -0500 Subject: [PATCH 08/21] Add @pvbouwel to authors. --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index cfb38777d..a0a5d8f15 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -30,3 +30,4 @@ Moto is written by Steve Pulec with contributions from: * [Omer Katz](https://github.com/thedrow) * [Joseph Lawson](https://github.com/joekiller) * [Trey Tacon](https://github.com/ttacon) +* [Peter](https://github.com/pvbouwel) \ No newline at end of file From 8bc8f09b474c322828aa0c15e74678c5b2da92a2 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 09:35:52 -0500 Subject: [PATCH 09/21] Some flake8 cleanup. --- moto/__init__.py | 32 ++++++++-------- moto/autoscaling/__init__.py | 2 +- moto/core/__init__.py | 2 +- moto/dynamodb2/models.py | 38 ++++++++++--------- moto/dynamodb2/responses.py | 28 +++++++------- moto/ec2/__init__.py | 3 +- moto/ec2/exceptions.py | 2 +- moto/ec2/models.py | 27 ++++++------- moto/ec2/responses/elastic_block_store.py | 4 +- moto/ec2/responses/elastic_ip_addresses.py | 8 ++-- .../responses/elastic_network_interfaces.py | 4 +- moto/ec2/responses/internet_gateways.py | 1 - moto/ec2/responses/route_tables.py | 4 +- moto/ec2/responses/vpc_peering_connections.py | 3 +- moto/ec2/utils.py | 2 +- moto/s3/responses.py | 8 ++-- moto/sqs/exceptions.py | 2 + moto/sqs/models.py | 2 +- requirements-dev.txt | 1 + tox.ini | 3 ++ 20 files changed, 90 insertions(+), 86 deletions(-) diff --git a/moto/__init__.py b/moto/__init__.py index df128c259..6daf3e290 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -2,19 +2,19 @@ from __future__ import unicode_literals import logging logging.getLogger('boto').setLevel(logging.CRITICAL) -from .autoscaling import mock_autoscaling -from .cloudformation import mock_cloudformation -from .cloudwatch import mock_cloudwatch -from .dynamodb import mock_dynamodb -from .dynamodb2 import mock_dynamodb2 -from .ec2 import mock_ec2 -from .elb import mock_elb -from .emr import mock_emr -from .iam import mock_iam -from .s3 import mock_s3 -from .s3bucket_path import mock_s3bucket_path -from .ses import mock_ses -from .sns import mock_sns -from .sqs import mock_sqs -from .sts import mock_sts -from .route53 import mock_route53 +from .autoscaling import mock_autoscaling # flake8: noqa +from .cloudformation import mock_cloudformation # flake8: noqa +from .cloudwatch import mock_cloudwatch # flake8: noqa +from .dynamodb import mock_dynamodb # flake8: noqa +from .dynamodb2 import mock_dynamodb2 # flake8: noqa +from .ec2 import mock_ec2 # flake8: noqa +from .elb import mock_elb # flake8: noqa +from .emr import mock_emr # flake8: noqa +from .iam import mock_iam # flake8: noqa +from .s3 import mock_s3 # flake8: noqa +from .s3bucket_path import mock_s3bucket_path # flake8: noqa +from .ses import mock_ses # flake8: noqa +from .sns import mock_sns # flake8: noqa +from .sqs import mock_sqs # flake8: noqa +from .sts import mock_sts # flake8: noqa +from .route53 import mock_route53 # flake8: noqa diff --git a/moto/autoscaling/__init__.py b/moto/autoscaling/__init__.py index ecd82ab0b..64e8e9b18 100644 --- a/moto/autoscaling/__init__.py +++ b/moto/autoscaling/__init__.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from .models import autoscaling_backend, autoscaling_backends +from .models import autoscaling_backend, autoscaling_backends # flake8: noqa from ..core.models import MockAWS diff --git a/moto/core/__init__.py b/moto/core/__init__.py index 7e8c31109..1b909183e 100644 --- a/moto/core/__init__.py +++ b/moto/core/__init__.py @@ -1,2 +1,2 @@ from __future__ import unicode_literals -from .models import BaseBackend +from .models import BaseBackend # flake8: noqa diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 1da4e577c..8e5ecd4b6 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -4,10 +4,10 @@ import datetime import json try: - from collections import OrderedDict + from collections import OrderedDict except ImportError: - # python 2.6 or earlier, use backport - from ordereddict import OrderedDict + # python 2.6 or earlier, use backport + from ordereddict import OrderedDict from moto.core import BaseBackend @@ -69,6 +69,7 @@ class DynamoType(object): comparison_func = get_comparison_func(range_comparison) return comparison_func(self.value, *range_values) + class Item(object): def __init__(self, hash_key, hash_key_type, range_key, range_key_type, attrs): self.hash_key = hash_key @@ -104,9 +105,10 @@ class Item(object): "Item": included } + class Table(object): - def __init__(self, table_name, schema=None, attr = None, throughput=None, indexes=None): + def __init__(self, table_name, schema=None, attr=None, throughput=None, indexes=None): self.name = table_name self.attr = attr self.schema = schema @@ -122,7 +124,7 @@ class Table(object): self.range_key_attr = elem["AttributeName"] self.range_key_type = elem["KeyType"] if throughput is None: - self.throughput = {u'WriteCapacityUnits': 10, u'ReadCapacityUnits': 10} + self.throughput = {'WriteCapacityUnits': 10, 'ReadCapacityUnits': 10} else: self.throughput = throughput self.throughput["NumberOfDecreasesToday"] = 0 @@ -133,15 +135,15 @@ class Table(object): @property def describe(self): results = { - 'Table': { - 'AttributeDefinitions': self.attr, - 'ProvisionedThroughput': self.throughput, - 'TableSizeBytes': 0, - 'TableName': self.name, - 'TableStatus': 'ACTIVE', - 'KeySchema': self.schema, - 'ItemCount': len(self), - 'CreationDateTime': unix_time(self.created_at) + 'Table': { + 'AttributeDefinitions': self.attr, + 'ProvisionedThroughput': self.throughput, + 'TableSizeBytes': 0, + 'TableName': self.name, + 'TableStatus': 'ACTIVE', + 'KeySchema': self.schema, + 'ItemCount': len(self), + 'CreationDateTime': unix_time(self.created_at), } } return results @@ -204,7 +206,7 @@ class Table(object): results = [] last_page = True # Once pagination is implemented, change this - possible_results = [ item for item in list(self.all_items()) if item.hash_key == hash_key] + possible_results = [item for item in list(self.all_items()) if item.hash_key == hash_key] if range_comparison: for result in possible_results: if result.range_key.compare(range_comparison, range_objs): @@ -285,17 +287,17 @@ class DynamoDBBackend(BaseBackend): return table.hash_key_attr, table.range_key_attr def get_keys_value(self, table, keys): - if not table.hash_key_attr in keys or (table.has_range_key and not table.range_key_attr in keys): + if table.hash_key_attr not in keys or (table.has_range_key and table.range_key_attr not in keys): raise ValueError("Table has a range key, but no range key was passed into get_item") hash_key = DynamoType(keys[table.hash_key_attr]) range_key = DynamoType(keys[table.range_key_attr]) if table.has_range_key else None - return hash_key,range_key + return hash_key, range_key def get_item(self, table_name, keys): table = self.tables.get(table_name) if not table: return None - hash_key,range_key = self.get_keys_value(table,keys) + hash_key, range_key = self.get_keys_value(table, keys) return table.get_item(hash_key, range_key) def query(self, table_name, hash_key_dict, range_comparison, range_value_dicts): diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 916482b7d..9d6d31f73 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -90,19 +90,19 @@ class DynamoHandler(BaseResponse): def create_table(self): body = self.body - #get the table name + # get the table name table_name = body['TableName'] - #get the throughput + # get the throughput throughput = body["ProvisionedThroughput"] - #getting the schema + # getting the schema key_schema = body['KeySchema'] - #getting attribute definition + # getting attribute definition attr = body["AttributeDefinitions"] - #getting the indexes + # getting the indexes table = dynamodb_backend2.create_table(table_name, - schema = key_schema, - throughput = throughput, - attr = attr) + schema=key_schema, + throughput=throughput, + attr=attr) return dynamo_json_dump(table.describe) def delete_table(self): @@ -169,6 +169,7 @@ class DynamoHandler(BaseResponse): } return dynamo_json_dump(response) + def get_item(self): name = self.body['TableName'] key = self.body['Key'] @@ -178,7 +179,7 @@ class DynamoHandler(BaseResponse): er = 'com.amazon.coral.validate#ValidationException' return self.error(er, status=400) if item: - item_dict = item.describe_attrs(attributes = None) + item_dict = item.describe_attrs(attributes=None) item_dict['ConsumedCapacityUnits'] = 0.5 return dynamo_json_dump(item_dict) else: @@ -190,7 +191,7 @@ class DynamoHandler(BaseResponse): table_batches = self.body['RequestItems'] results = { - "ConsumedCapacity":[], + "ConsumedCapacity": [], "Responses": { }, "UnprocessedKeys": { @@ -198,10 +199,9 @@ class DynamoHandler(BaseResponse): } for table_name, table_request in table_batches.items(): - items = [] keys = table_request['Keys'] attributes_to_get = table_request.get('AttributesToGet') - results["Responses"][table_name]=[] + results["Responses"][table_name] = [] for key in keys: item = dynamodb_backend2.get_item(table_name, key) if item: @@ -226,7 +226,7 @@ class DynamoHandler(BaseResponse): range_comparison = None range_values = [] else: - if range_key_name == None: + if range_key_name is None: er = "com.amazon.coral.validate#ValidationException" return self.error(er) else: @@ -247,7 +247,7 @@ class DynamoHandler(BaseResponse): items = items[:limit] reversed = self.body.get("ScanIndexForward") - if reversed != False: + if reversed is not False: items.reverse() result = { diff --git a/moto/ec2/__init__.py b/moto/ec2/__init__.py index 649c8737a..53547cafe 100644 --- a/moto/ec2/__init__.py +++ b/moto/ec2/__init__.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals -from .models import ec2_backends, ec2_backend +from .models import ec2_backend, ec2_backends # flake8: noqa from ..core.models import MockAWS + def mock_ec2(func=None): if func: return MockAWS(ec2_backends)(func) diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 306e898e0..b587f0a7f 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -163,7 +163,7 @@ class InvalidSnapshotIdError(EC2ClientError): def __init__(self, snapshot_id): super(InvalidSnapshotIdError, self).__init__( "InvalidSnapshot.NotFound", - "") # Note: AWS returns empty message for this, as of 2014.08.22. + "") # Note: AWS returns empty message for this, as of 2014.08.22. class InvalidVolumeIdError(EC2ClientError): diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 309cab6bf..d897b7191 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals -import copy -import itertools + from collections import defaultdict +import copy from datetime import datetime +import itertools import re import six @@ -11,6 +12,7 @@ from boto.ec2.instance import Instance as BotoInstance, Reservation from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest from boto.ec2.launchspecification import LaunchSpecification +import six from moto.core import BaseBackend from moto.core.models import Model @@ -240,12 +242,11 @@ class NetworkInterfaceBackend(object): for (_filter, _filter_value) in filters.items(): if _filter == 'network-interface-id': _filter = 'id' - enis = [ eni for eni in enis if getattr(eni, _filter) in _filter_value ] + enis = [eni for eni in enis if getattr(eni, _filter) in _filter_value] elif _filter == 'group-id': original_enis = enis enis = [] for eni in original_enis: - group_ids = [] for group in eni.group_set: if group.id in _filter_value: enis.append(eni) @@ -817,7 +818,7 @@ class Ami(TaggedEC2Resource): elif filter_name == 'kernel-id': return self.kernel_id elif filter_name in ['architecture', 'platform']: - return getattr(self,filter_name) + return getattr(self, filter_name) elif filter_name == 'image-id': return self.id elif filter_name == 'state': @@ -856,7 +857,6 @@ class AmiBackend(object): def describe_images(self, ami_ids=(), filters=None): if filters: images = self.amis.values() - return generic_filter(filters, images) else: images = [] @@ -1008,10 +1008,7 @@ class SecurityGroup(object): def physical_resource_id(self): return self.id - def matches_filter(self, key, filter_value): - result = True - def to_attr(filter_name): attr = None @@ -1031,7 +1028,7 @@ class SecurityGroup(object): ingress_attr = to_attr(match.groups()[0]) for ingress in self.ingress_rules: - if getattr(ingress, ingress_attr) in filters[key]: + if getattr(ingress, ingress_attr) in filter_value: return True else: attr_name = to_attr(key) @@ -1302,7 +1299,7 @@ class EBSBackend(object): def detach_volume(self, volume_id, instance_id, device_path): volume = self.get_volume(volume_id) - instance = self.get_instance(instance_id) + self.get_instance(instance_id) old_attachment = volume.attachment if not old_attachment: @@ -1406,7 +1403,7 @@ class VPCBackend(object): self.vpcs[vpc_id] = vpc # AWS creates a default main route table and security group. - main_route_table = self.create_route_table(vpc_id, main=True) + self.create_route_table(vpc_id, main=True) default = self.get_security_group_from_name('default', vpc_id=vpc_id) if not default: @@ -1429,7 +1426,7 @@ class VPCBackend(object): def delete_vpc(self, vpc_id): # Delete route table if only main route table remains. - route_tables = self.get_all_route_tables(filters={'vpc-id':vpc_id}) + route_tables = self.get_all_route_tables(filters={'vpc-id': vpc_id}) if len(route_tables) > 1: raise DependencyViolationError( "The vpc {0} has dependencies and cannot be deleted." @@ -1599,7 +1596,7 @@ class SubnetBackend(object): def create_subnet(self, vpc_id, cidr_block): subnet_id = random_subnet_id() subnet = Subnet(self, subnet_id, vpc_id, cidr_block) - vpc = self.get_vpc(vpc_id) # Validate VPC exists + self.get_vpc(vpc_id) # Validate VPC exists self.subnets[subnet_id] = subnet return subnet @@ -1719,7 +1716,7 @@ class RouteTableBackend(object): route_tables = self.route_tables.values() if route_table_ids: - route_tables = [ route_table for route_table in route_tables if route_table.id in route_table_ids ] + route_tables = [route_table for route_table in route_tables if route_table.id in route_table_ids] if len(route_tables) != len(route_table_ids): invalid_id = list(set(route_table_ids).difference(set([route_table.id for route_table in route_tables])))[0] raise InvalidRouteTableIdError(invalid_id) diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index 69a4cb9e8..3684c042c 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -35,12 +35,12 @@ class ElasticBlockStore(BaseResponse): def delete_snapshot(self): snapshot_id = self.querystring.get('SnapshotId')[0] - success = self.ec2_backend.delete_snapshot(snapshot_id) + self.ec2_backend.delete_snapshot(snapshot_id) return DELETE_SNAPSHOT_RESPONSE def delete_volume(self): volume_id = self.querystring.get('VolumeId')[0] - success = self.ec2_backend.delete_volume(volume_id) + self.ec2_backend.delete_volume(volume_id) return DELETE_VOLUME_RESPONSE def describe_snapshots(self): diff --git a/moto/ec2/responses/elastic_ip_addresses.py b/moto/ec2/responses/elastic_ip_addresses.py index 1c2cbfe0b..eee46107f 100644 --- a/moto/ec2/responses/elastic_ip_addresses.py +++ b/moto/ec2/responses/elastic_ip_addresses.py @@ -59,9 +59,9 @@ class ElasticIPAddresses(BaseResponse): def disassociate_address(self): if "PublicIp" in self.querystring: - disassociated = self.ec2_backend.disassociate_address(address=self.querystring['PublicIp'][0]) + self.ec2_backend.disassociate_address(address=self.querystring['PublicIp'][0]) elif "AssociationId" in self.querystring: - disassociated = self.ec2_backend.disassociate_address(association_id=self.querystring['AssociationId'][0]) + self.ec2_backend.disassociate_address(association_id=self.querystring['AssociationId'][0]) else: self.ec2_backend.raise_error("MissingParameter", "Invalid request, expect PublicIp/AssociationId parameter.") @@ -69,9 +69,9 @@ class ElasticIPAddresses(BaseResponse): def release_address(self): if "PublicIp" in self.querystring: - released = self.ec2_backend.release_address(address=self.querystring['PublicIp'][0]) + self.ec2_backend.release_address(address=self.querystring['PublicIp'][0]) elif "AllocationId" in self.querystring: - released = self.ec2_backend.release_address(allocation_id=self.querystring['AllocationId'][0]) + self.ec2_backend.release_address(allocation_id=self.querystring['AllocationId'][0]) else: self.ec2_backend.raise_error("MissingParameter", "Invalid request, expect PublicIp/AllocationId parameter.") diff --git a/moto/ec2/responses/elastic_network_interfaces.py b/moto/ec2/responses/elastic_network_interfaces.py index a42f12bda..e023cc885 100644 --- a/moto/ec2/responses/elastic_network_interfaces.py +++ b/moto/ec2/responses/elastic_network_interfaces.py @@ -25,7 +25,7 @@ class ElasticNetworkInterfaces(BaseResponse): raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).describe_network_interface_attribute is not yet implemented') def describe_network_interfaces(self): - #Partially implemented. Supports only network-interface-id and group-id filters + # Partially implemented. Supports only network-interface-id and group-id filters filters = filters_from_querystring(self.querystring) enis = self.ec2_backend.describe_network_interfaces(filters) template = Template(DESCRIBE_NETWORK_INTERFACES_RESPONSE) @@ -46,7 +46,7 @@ class ElasticNetworkInterfaces(BaseResponse): return template.render() def modify_network_interface_attribute(self): - #Currently supports modifying one and only one security group + # Currently supports modifying one and only one security group eni_id = self.querystring.get('NetworkInterfaceId')[0] group_id = self.querystring.get('SecurityGroupId.1')[0] self.ec2_backend.modify_network_interface_attribute(eni_id, group_id) diff --git a/moto/ec2/responses/internet_gateways.py b/moto/ec2/responses/internet_gateways.py index d55e29f49..bad658970 100644 --- a/moto/ec2/responses/internet_gateways.py +++ b/moto/ec2/responses/internet_gateways.py @@ -112,4 +112,3 @@ DETACH_INTERNET_GATEWAY_RESPONSE = u"""true """ - diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index bb15f7920..3999c7e43 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -130,7 +130,7 @@ def generate_route_id(route_table_id, cidr_block): def split_route_id(route_id): - values = string.split(route_id, '~') + values = route_id.split('~') return values[0], values[1] diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 1a7ef7444..daa53d2da 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -166,12 +166,12 @@ class ResponseObject(object): if request.path == u'/?delete': return self._bucket_response_delete_keys(request, bucket_name, headers) - #POST to bucket-url should create file from form + # POST to bucket-url should create file from form if hasattr(request, 'form'): - #Not HTTPretty + # Not HTTPretty form = request.form else: - #HTTPretty, build new form object + # HTTPretty, build new form object form = {} for kv in request.body.decode('utf-8').split('&'): k, v = kv.split('=') @@ -185,7 +185,7 @@ class ResponseObject(object): new_key = self.backend.set_key(bucket_name, key, f) - #Metadata + # Metadata meta_regex = re.compile('^x-amz-meta-([a-zA-Z0-9\-_]+)$', flags=re.IGNORECASE) for form_id in form: diff --git a/moto/sqs/exceptions.py b/moto/sqs/exceptions.py index 7b2c121dc..7ae661cc5 100644 --- a/moto/sqs/exceptions.py +++ b/moto/sqs/exceptions.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals + + class MessageNotInflight(Exception): description = "The message referred to is not in flight." status_code = 400 diff --git a/moto/sqs/models.py b/moto/sqs/models.py index a6188a57d..cbcb61892 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -import base64 + import hashlib import time import re diff --git a/requirements-dev.txt b/requirements-dev.txt index ed840dcf4..17517f01e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,3 +4,4 @@ nose sure<1.2.4 coverage freezegun +flask diff --git a/tox.ini b/tox.ini index 9cdaafb1b..98900b1d2 100644 --- a/tox.ini +++ b/tox.ini @@ -6,3 +6,6 @@ deps = -r{toxinidir}/requirements.txt commands = {envpython} setup.py test nosetests + +[flake8] +ignore = E128,E501 From 8ba308bf073a2f1d97158c15537f406053d7bf92 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 09:43:35 -0500 Subject: [PATCH 10/21] EC2 flake8 fixes. --- moto/ec2/models.py | 54 +++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index d897b7191..1a367ebd8 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -6,7 +6,6 @@ from datetime import datetime import itertools import re -import six import boto from boto.ec2.instance import Instance as BotoInstance, Reservation from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType @@ -103,6 +102,7 @@ class InstanceState(object): self.name = name self.code = code + class StateReason(object): def __init__(self, message="", code=""): self.message = message @@ -134,7 +134,7 @@ class TaggedEC2Resource(object): class NetworkInterface(object): def __init__(self, ec2_backend, subnet, private_ip_address, device_index=0, - public_ip_auto_assign=True, group_ids=None): + public_ip_auto_assign=True, group_ids=None): self.ec2_backend = ec2_backend self.id = random_eni_id() self.device_index = device_index @@ -319,9 +319,9 @@ class Instance(BotoInstance, TaggedEC2Resource): self.vpc_id = subnet.vpc_id self.prep_nics(kwargs.get("nics", {}), - subnet_id=kwargs.get("subnet_id",None), - private_ip=kwargs.get("private_ip",None), - associate_public_ip=kwargs.get("associate_public_ip",None)) + subnet_id=kwargs.get("subnet_id"), + private_ip=kwargs.get("private_ip"), + associate_public_ip=kwargs.get("associate_public_ip")) @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -402,7 +402,7 @@ class Instance(BotoInstance, TaggedEC2Resource): primary_nic = {'SubnetId': subnet_id, 'PrivateIpAddress': private_ip, 'AssociatePublicIpAddress': associate_public_ip} - primary_nic = dict((k,v) for k, v in primary_nic.items() if v) + primary_nic = dict((k, v) for k, v in primary_nic.items() if v) # If empty NIC spec but primary NIC values provided, create NIC from them. if primary_nic and not nic_spec: @@ -411,12 +411,9 @@ class Instance(BotoInstance, TaggedEC2Resource): # Flesh out data structures and associations for nic in nic_spec.values(): - use_eni = None - security_group_ids = [] - device_index = int(nic.get('DeviceIndex')) - nic_id = nic.get('NetworkInterfaceId', None) + nic_id = nic.get('NetworkInterfaceId') if nic_id: # If existing NIC found, use it. use_nic = self.ec2_backend.get_network_interface(nic_id) @@ -430,13 +427,13 @@ class Instance(BotoInstance, TaggedEC2Resource): subnet = self.ec2_backend.get_subnet(nic['SubnetId']) - group_id = nic.get('SecurityGroupId',None) + group_id = nic.get('SecurityGroupId') group_ids = [group_id] if group_id else [] use_nic = self.ec2_backend.create_network_interface(subnet, - nic.get('PrivateIpAddress',None), + nic.get('PrivateIpAddress'), device_index=device_index, - public_ip_auto_assign=nic.get('AssociatePublicIpAddress',False), + public_ip_auto_assign=nic.get('AssociatePublicIpAddress', False), group_ids=group_ids) self.attach_eni(use_nic, device_index) @@ -445,14 +442,14 @@ class Instance(BotoInstance, TaggedEC2Resource): device_index = int(device_index) self.nics[device_index] = eni - eni.instance = self # This is used upon associate/disassociate public IP. + eni.instance = self # This is used upon associate/disassociate public IP. eni.attachment_id = random_eni_attach_id() eni.device_index = device_index return eni.attachment_id def detach_eni(self, eni): - self.nics.pop(eni.device_index,None) + self.nics.pop(eni.device_index, None) eni.instance = None eni.attachment_id = None eni.device_index = None @@ -712,7 +709,7 @@ class TagBackend(object): resource_id_filters = [] resource_type_filters = [] value_filters = [] - if not filters is None: + if filters is not None: for tag_filter in filters: if tag_filter in self.VALID_TAG_FILTERS: if tag_filter == 'key': @@ -771,7 +768,7 @@ class TagBackend(object): 'key': key, 'value': value, 'resource_type': EC2_PREFIX_TO_RESOURCE[get_prefix(resource_id)], - } + } results.append(result) return results @@ -1485,8 +1482,8 @@ class VPCPeeringConnection(TaggedEC2Resource): def create_from_cloudformation_json(cls, resource_name, cloudformation_json): properties = cloudformation_json['Properties'] - vpc = get_vpc(properties['VpcId']) - peer_vpc = get_vpc(properties['PeerVpcId']) + vpc = ec2_backend.get_vpc(properties['VpcId']) + peer_vpc = ec2_backend.get_vpc(properties['PeerVpcId']) vpc_pcx = ec2_backend.create_vpc_peering_connection(vpc, peer_vpc) @@ -1596,7 +1593,7 @@ class SubnetBackend(object): def create_subnet(self, vpc_id, cidr_block): subnet_id = random_subnet_id() subnet = Subnet(self, subnet_id, vpc_id, cidr_block) - self.get_vpc(vpc_id) # Validate VPC exists + self.get_vpc(vpc_id) # Validate VPC exists self.subnets[subnet_id] = subnet return subnet @@ -1697,7 +1694,7 @@ class RouteTableBackend(object): def create_route_table(self, vpc_id, main=False): route_table_id = random_route_table_id() - vpc = self.get_vpc(vpc_id) # Validate VPC exists + vpc = self.get_vpc(vpc_id) # Validate VPC exists route_table = RouteTable(self, route_table_id, vpc_id, main=main) self.route_tables[route_table_id] = route_table @@ -1735,15 +1732,15 @@ class RouteTableBackend(object): def associate_route_table(self, route_table_id, subnet_id): # Idempotent if association already exists. - route_tables_by_subnet = self.get_all_route_tables(filters={'association.subnet-id':[subnet_id]}) + route_tables_by_subnet = self.get_all_route_tables(filters={'association.subnet-id': [subnet_id]}) if route_tables_by_subnet: - for association_id,check_subnet_id in route_tables_by_subnet[0].associations.items(): + for association_id, check_subnet_id in route_tables_by_subnet[0].associations.items(): if subnet_id == check_subnet_id: return association_id # Association does not yet exist, so create it. route_table = self.get_route_table(route_table_id) - subnet = self.get_subnet(subnet_id) # Validate subnet exists + self.get_subnet(subnet_id) # Validate subnet exists association_id = random_subnet_association_id() route_table.associations[association_id] = subnet_id return association_id @@ -1761,13 +1758,13 @@ class RouteTableBackend(object): return association_id # Find route table which currently has the association, error if none. - route_tables_by_association_id = self.get_all_route_tables(filters={'association.route-table-association-id':[association_id]}) + route_tables_by_association_id = self.get_all_route_tables(filters={'association.route-table-association-id': [association_id]}) if not route_tables_by_association_id: raise InvalidAssociationIdError(association_id) # Remove existing association, create new one. previous_route_table = route_tables_by_association_id[0] - subnet_id = previous_route_table.associations.pop(association_id,None) + subnet_id = previous_route_table.associations.pop(association_id, None) return self.associate_route_table(route_table_id, subnet_id) @@ -2031,8 +2028,7 @@ class SpotRequestBackend(object): 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 - ) + monitoring_enabled, subnet_id) self.spot_instance_requests[spot_request_id] = request requests.append(request) return requests @@ -2064,7 +2060,7 @@ class ElasticAddress(object): properties = cloudformation_json.get('Properties') instance_id = None if properties: - domain=properties.get('Domain') + domain = properties.get('Domain') eip = ec2_backend.allocate_address( domain=domain if domain else 'standard') instance_id = properties.get('InstanceId') From c77207a8b83f6645bdaf74c6e36abe968abf920e Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 09:53:45 -0500 Subject: [PATCH 11/21] More flake8. --- moto/autoscaling/models.py | 1 - moto/cloudformation/exceptions.py | 1 - moto/cloudformation/parsing.py | 6 +++--- moto/ec2/responses/route_tables.py | 16 ++++++++-------- moto/ec2/utils.py | 8 ++++---- moto/iam/models.py | 2 +- moto/sqs/exceptions.py | 6 +++--- moto/sqs/utils.py | 2 +- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index 1e2cf689e..bcd40e892 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -360,4 +360,3 @@ for region, ec2_backend in ec2_backends.items(): autoscaling_backend = autoscaling_backends['us-east-1'] default_autoscaling_backend = autoscaling_backend - diff --git a/moto/cloudformation/exceptions.py b/moto/cloudformation/exceptions.py index c8bb5b397..8fc7dce0c 100644 --- a/moto/cloudformation/exceptions.py +++ b/moto/cloudformation/exceptions.py @@ -3,7 +3,6 @@ from boto.exception import BotoServerError from jinja2 import Template - class UnformattedGetAttTemplateException(Exception): description = 'Template error: resource {0} does not support attribute type {1} in Fn::GetAtt' status_code = 400 diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 97c707965..dc07aa4e3 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -129,9 +129,9 @@ def parse_resource(logical_id, resource_json, resources_map): resource_json = clean_json(resource_json, resources_map) resource_name_property = resource_name_property_from_type(resource_type) if resource_name_property: - if not 'Properties' in resource_json: + if 'Properties' not in resource_json: resource_json['Properties'] = dict() - if not resource_name_property in resource_json['Properties']: + if resource_name_property not in resource_json['Properties']: resource_json['Properties'][resource_name_property] = '{0}-{1}-{2}'.format( resources_map.get('AWS::StackName'), logical_id, @@ -214,7 +214,7 @@ class ResourceMap(collections.Mapping): self[resource] if isinstance(self[resource], ec2_models.TaggedEC2Resource): tags['aws:cloudformation:logical-id'] = resource - ec2_models.ec2_backend.create_tags([self[resource].physical_resource_id],tags) + ec2_models.ec2_backend.create_tags([self[resource].physical_resource_id], tags) class OutputMap(collections.Mapping): diff --git a/moto/ec2/responses/route_tables.py b/moto/ec2/responses/route_tables.py index 010de62e7..ae9abff45 100644 --- a/moto/ec2/responses/route_tables.py +++ b/moto/ec2/responses/route_tables.py @@ -23,10 +23,10 @@ class RouteTables(BaseResponse): pcx_id = optional_from_querystring('VpcPeeringConnectionId', self.querystring) self.ec2_backend.create_route(route_table_id, destination_cidr_block, - gateway_id=internet_gateway_id, - instance_id=instance_id, - interface_id=interface_id, - vpc_peering_connection_id=pcx_id) + gateway_id=internet_gateway_id, + instance_id=instance_id, + interface_id=interface_id, + vpc_peering_connection_id=pcx_id) template = Template(CREATE_ROUTE_RESPONSE) return template.render() @@ -73,10 +73,10 @@ class RouteTables(BaseResponse): pcx_id = optional_from_querystring('VpcPeeringConnectionId', self.querystring) self.ec2_backend.replace_route(route_table_id, destination_cidr_block, - gateway_id=internet_gateway_id, - instance_id=instance_id, - interface_id=interface_id, - vpc_peering_connection_id=pcx_id) + gateway_id=internet_gateway_id, + instance_id=instance_id, + interface_id=interface_id, + vpc_peering_connection_id=pcx_id) template = Template(REPLACE_ROUTE_RESPONSE) return template.render() diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 3999c7e43..ee9c9143c 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -390,8 +390,8 @@ def generic_filter(filters, objects): def simple_aws_filter_to_re(filter_string): import fnmatch - tmp_filter = filter_string.replace('\?','[?]') - tmp_filter = tmp_filter.replace('\*','[*]') + tmp_filter = filter_string.replace('\?', '[?]') + tmp_filter = tmp_filter.replace('\*', '[*]') tmp_filter = fnmatch.translate(tmp_filter) return tmp_filter @@ -425,7 +425,7 @@ def get_prefix(resource_id): if resource_id_prefix == EC2_RESOURCE_TO_PREFIX['network-interface']: if after.startswith('attach'): resource_id_prefix = EC2_RESOURCE_TO_PREFIX['network-interface-attachment'] - if not resource_id_prefix in EC2_RESOURCE_TO_PREFIX.values(): + if resource_id_prefix not in EC2_RESOURCE_TO_PREFIX.values(): uuid4hex = re.compile('[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z', re.I) if uuid4hex.match(resource_id) is not None: resource_id_prefix = EC2_RESOURCE_TO_PREFIX['reserved-instance'] @@ -437,7 +437,7 @@ def get_prefix(resource_id): def is_valid_resource_id(resource_id): valid_prefixes = EC2_RESOURCE_TO_PREFIX.values() resource_id_prefix = get_prefix(resource_id) - if not resource_id_prefix in valid_prefixes: + if resource_id_prefix not in valid_prefixes: return False resource_id_pattern = resource_id_prefix + '-[0-9a-f]{8}' resource_pattern_re = re.compile(resource_id_pattern) diff --git a/moto/iam/models.py b/moto/iam/models.py index 84f3274c1..7c4f2ec8f 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -272,7 +272,7 @@ class IAMBackend(BaseBackend): return user def create_login_profile(self, user_name, password): - if not user_name in self.users: + if user_name not in self.users: raise BotoServerError(404, 'Not Found') # This does not currently deal with PasswordPolicyViolation. diff --git a/moto/sqs/exceptions.py b/moto/sqs/exceptions.py index 7ae661cc5..d72cfdffc 100644 --- a/moto/sqs/exceptions.py +++ b/moto/sqs/exceptions.py @@ -12,7 +12,7 @@ class ReceiptHandleIsInvalid(Exception): class MessageAttributesInvalid(Exception): - status_code = 400 + status_code = 400 - def __init__(self, description): - self.description = description + def __init__(self, description): + self.description = description diff --git a/moto/sqs/utils.py b/moto/sqs/utils.py index 294611a05..8dee7003f 100644 --- a/moto/sqs/utils.py +++ b/moto/sqs/utils.py @@ -52,7 +52,7 @@ def parse_message_attributes(querystring, base='', value_namespace='Value.'): if not value: raise MessageAttributesInvalid("The message attribute '{0}' must contain non-empty message attribute value for message attribute type '{1}'.".format(name[0], data_type[0])) - message_attributes[name[0]] = {'data_type' : data_type[0], type_prefix.lower() + '_value' : value[0]} + message_attributes[name[0]] = {'data_type': data_type[0], type_prefix.lower() + '_value': value[0]} index += 1 From b39861052b2737d34614bcb7d919a8e35a30ab92 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 10:06:14 -0500 Subject: [PATCH 12/21] Make AMI test more explicit with region. --- tests/test_ec2/test_amis.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py index 3bdf6dac7..2aa787d84 100644 --- a/tests/test_ec2/test_amis.py +++ b/tests/test_ec2/test_amis.py @@ -1,9 +1,10 @@ from __future__ import unicode_literals # Ensure 'assert_raises' context manager support for Python 2.6 -import tests.backport_assert_raises +import tests.backport_assert_raises # noqa from nose.tools import assert_raises import boto +import boto.ec2 from boto.exception import EC2ResponseError import sure # noqa @@ -55,7 +56,7 @@ def test_ami_create_and_delete(): @requires_boto_gte("2.14.0") @mock_ec2 def test_ami_copy(): - conn = boto.connect_ec2('the_key', 'the_secret') + conn = boto.ec2.connect_to_region("us-west-1") reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] @@ -183,6 +184,7 @@ def test_ami_filters(): amis_by_name = conn.get_all_images(filters={'name': imageA.name}) set([ami.id for ami in amis_by_name]).should.equal(set([imageA.id])) + @mock_ec2 def test_ami_filtering_via_tag(): conn = boto.connect_vpc('the_key', 'the_secret') @@ -309,4 +311,3 @@ def test_ami_attribute(): attribute='launchPermission', operation='remove', user_ids=['user']).should.throw(NotImplementedError) - From bd847bd941936d844c123313b8e3db6e0207003e Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 13:34:52 -0500 Subject: [PATCH 13/21] Cleanup multi-region support for ELB, SQS, Cloudformation, EC2, Autoscaling. --- moto/autoscaling/models.py | 10 +++-- moto/cloudformation/__init__.py | 11 ++++- moto/cloudformation/models.py | 22 +++++++--- moto/cloudformation/parsing.py | 9 ++-- moto/cloudformation/responses.py | 21 +++++---- moto/ec2/models.py | 43 +++++++++++++------ moto/elb/__init__.py | 11 ++++- moto/elb/models.py | 12 +++++- moto/elb/responses.py | 24 ++++++----- moto/iam/models.py | 4 +- moto/sqs/__init__.py | 11 ++++- moto/sqs/models.py | 10 ++++- moto/sqs/responses.py | 35 +++++++++------ .../test_cloudformation_stack_crud.py | 17 +++++++- .../test_cloudformation_stack_integration.py | 21 +++------ .../test_cloudformation/test_stack_parsing.py | 13 +++--- 16 files changed, 184 insertions(+), 90 deletions(-) diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index bcd40e892..09110b016 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -47,12 +47,13 @@ class FakeLaunchConfiguration(object): self.block_device_mapping_dict = block_device_mapping_dict @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] instance_profile_name = properties.get("IamInstanceProfile") - config = default_autoscaling_backend.create_launch_configuration( + backend = autoscaling_backends[region_name] + config = backend.create_launch_configuration( name=resource_name, image_id=properties.get("ImageId"), key_name=properties.get("KeyName"), @@ -128,13 +129,14 @@ class FakeAutoScalingGroup(object): self.set_desired_capacity(desired_capacity) @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] launch_config_name = properties.get("LaunchConfigurationName") load_balancer_names = properties.get("LoadBalancerNames", []) - group = default_autoscaling_backend.create_autoscaling_group( + backend = autoscaling_backends[region_name] + group = backend.create_autoscaling_group( name=resource_name, availability_zones=properties.get("AvailabilityZones", []), desired_capacity=properties.get("DesiredCapacity"), diff --git a/moto/cloudformation/__init__.py b/moto/cloudformation/__init__.py index 17d520ba7..1d7ff041f 100644 --- a/moto/cloudformation/__init__.py +++ b/moto/cloudformation/__init__.py @@ -1,3 +1,10 @@ from __future__ import unicode_literals -from .models import cloudformation_backend -mock_cloudformation = cloudformation_backend.decorator +from .models import cloudformation_backends, cloudformation_backend # flake8: noqa +from ..core.models import MockAWS + + +def mock_cloudformation(func=None): + if func: + return MockAWS(cloudformation_backends)(func) + else: + return MockAWS(cloudformation_backends) diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index 81fa8f840..aeb130e23 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import json +import boto.cloudformation from moto.core import BaseBackend from .parsing import ResourceMap, OutputMap @@ -9,9 +10,10 @@ from .exceptions import ValidationError class FakeStack(object): - def __init__(self, stack_id, name, template, notification_arns=None): + def __init__(self, stack_id, name, template, region_name, notification_arns=None): self.stack_id = stack_id self.name = name + self.region_name = region_name self.notification_arns = notification_arns if notification_arns else [] self.template = template self.status = 'CREATE_COMPLETE' @@ -19,7 +21,7 @@ class FakeStack(object): template_dict = json.loads(self.template) self.description = template_dict.get('Description') - self.resource_map = ResourceMap(stack_id, name, template_dict) + self.resource_map = ResourceMap(stack_id, name, region_name, template_dict) self.resource_map.create() self.output_map = OutputMap(self.resource_map, template_dict) @@ -40,9 +42,15 @@ class CloudFormationBackend(BaseBackend): self.stacks = {} self.deleted_stacks = {} - def create_stack(self, name, template, notification_arns=None): + def create_stack(self, name, template, region_name, notification_arns=None): stack_id = generate_stack_id(name) - new_stack = FakeStack(stack_id=stack_id, name=name, template=template, notification_arns=notification_arns) + new_stack = FakeStack( + stack_id=stack_id, + name=name, + template=template, + region_name=region_name, + notification_arns=notification_arns, + ) self.stacks[stack_id] = new_stack return new_stack @@ -90,4 +98,8 @@ class CloudFormationBackend(BaseBackend): self.delete_stack(stack_to_delete.stack_id) -cloudformation_backend = CloudFormationBackend() +cloudformation_backends = {} +for region in boto.cloudformation.regions(): + cloudformation_backends[region.name] = CloudFormationBackend() + +cloudformation_backend = cloudformation_backends['us-east-1'] diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index dc07aa4e3..07bf72e35 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -120,7 +120,7 @@ def resource_name_property_from_type(resource_type): return NAME_TYPE_MAP.get(resource_type) -def parse_resource(logical_id, resource_json, resources_map): +def parse_resource(logical_id, resource_json, resources_map, region_name): resource_type = resource_json['Type'] resource_class = resource_class_from_type(resource_type) if not resource_class: @@ -142,7 +142,7 @@ def parse_resource(logical_id, resource_json, resources_map): logical_id, random_suffix()) - resource = resource_class.create_from_cloudformation_json(resource_name, resource_json) + resource = resource_class.create_from_cloudformation_json(resource_name, resource_json, region_name) resource.type = resource_type resource.logical_resource_id = logical_id return resource @@ -164,9 +164,10 @@ class ResourceMap(collections.Mapping): each resources is passed this lazy map that it can grab dependencies from. """ - def __init__(self, stack_id, stack_name, template): + def __init__(self, stack_id, stack_name, region_name, template): self._template = template self._resource_json_map = template['Resources'] + self._region_name = region_name # Create the default resources self._parsed_resources = { @@ -183,7 +184,7 @@ class ResourceMap(collections.Mapping): return self._parsed_resources[resource_logical_id] else: resource_json = self._resource_json_map.get(resource_logical_id) - new_resource = parse_resource(resource_logical_id, resource_json, self) + new_resource = parse_resource(resource_logical_id, resource_json, self, self._region_name) self._parsed_resources[resource_logical_id] = new_resource return new_resource diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index a9f2cafee..48e8686de 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -4,19 +4,24 @@ import json from jinja2 import Template from moto.core.responses import BaseResponse -from .models import cloudformation_backend +from .models import cloudformation_backends class CloudFormationResponse(BaseResponse): + @property + def cloudformation_backend(self): + return cloudformation_backends[self.region] + def create_stack(self): stack_name = self._get_param('StackName') stack_body = self._get_param('TemplateBody') stack_notification_arns = self._get_multi_param('NotificationARNs.member') - stack = cloudformation_backend.create_stack( + stack = self.cloudformation_backend.create_stack( name=stack_name, template=stack_body, + region_name=self.region, notification_arns=stack_notification_arns ) stack_body = { @@ -32,34 +37,34 @@ class CloudFormationResponse(BaseResponse): stack_name_or_id = None if self._get_param('StackName'): stack_name_or_id = self.querystring.get('StackName')[0] - stacks = cloudformation_backend.describe_stacks(stack_name_or_id) + stacks = self.cloudformation_backend.describe_stacks(stack_name_or_id) template = Template(DESCRIBE_STACKS_TEMPLATE) return template.render(stacks=stacks) def describe_stack_resources(self): stack_name = self._get_param('StackName') - stack = cloudformation_backend.get_stack(stack_name) + stack = self.cloudformation_backend.get_stack(stack_name) template = Template(LIST_STACKS_RESOURCES_RESPONSE) return template.render(stack=stack) def list_stacks(self): - stacks = cloudformation_backend.list_stacks() + stacks = self.cloudformation_backend.list_stacks() template = Template(LIST_STACKS_RESPONSE) return template.render(stacks=stacks) def get_template(self): name_or_stack_id = self.querystring.get('StackName')[0] - stack = cloudformation_backend.get_stack(name_or_stack_id) + stack = self.cloudformation_backend.get_stack(name_or_stack_id) return stack.template # def update_stack(self): # stack_name = self._get_param('StackName') # stack_body = self._get_param('TemplateBody') - # stack = cloudformation_backend.update_stack( + # stack = self.cloudformation_backend.update_stack( # name=stack_name, # template=stack_body, # ) @@ -75,7 +80,7 @@ class CloudFormationResponse(BaseResponse): def delete_stack(self): name_or_stack_id = self.querystring.get('StackName')[0] - cloudformation_backend.delete_stack(name_or_stack_id) + self.cloudformation_backend.delete_stack(name_or_stack_id) return json.dumps({ 'DeleteStackResponse': { 'DeleteStackResult': {}, diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 1a367ebd8..cf380d3fa 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -165,12 +165,13 @@ class NetworkInterface(object): self._group_set.append(group) @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] security_group_ids = properties.get('SecurityGroups', []) subnet_id = properties['SubnetId'] + ec2_backend = ec2_backends[region_name] subnet = ec2_backend.get_subnet(subnet_id) private_ip_address = properties.get('PrivateIpAddress', None) @@ -324,9 +325,10 @@ class Instance(BotoInstance, TaggedEC2Resource): associate_public_ip=kwargs.get("associate_public_ip")) @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + ec2_backend = ec2_backends[region_name] security_group_ids = properties.get('SecurityGroups', []) group_names = [ec2_backend.get_security_group_from_id(group_id).name for group_id in security_group_ids] @@ -975,9 +977,10 @@ class SecurityGroup(object): self.vpc_id = vpc_id @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + ec2_backend = ec2_backends[region_name] vpc_id = properties.get('VpcId') security_group = ec2_backend.create_security_group( name=resource_name, @@ -1203,12 +1206,13 @@ class VolumeAttachment(object): self.device = device @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] instance_id = properties['InstanceId'] volume_id = properties['VolumeId'] + ec2_backend = ec2_backends[region_name] attachment = ec2_backend.attach_volume( volume_id=volume_id, instance_id=instance_id, @@ -1226,9 +1230,10 @@ class Volume(TaggedEC2Resource): self.ec2_backend = ec2_backend @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + ec2_backend = ec2_backends[region_name] volume = ec2_backend.create_volume( size=properties.get('Size'), zone_name=properties.get('AvailabilityZone'), @@ -1360,9 +1365,10 @@ class VPC(TaggedEC2Resource): self.state = 'available' @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + ec2_backend = ec2_backends[region_name] vpc = ec2_backend.create_vpc( cidr_block=properties['CidrBlock'], ) @@ -1479,9 +1485,10 @@ class VPCPeeringConnection(TaggedEC2Resource): self._status = VPCPeeringConnectionStatus() @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + ec2_backend = ec2_backends[region_name] vpc = ec2_backend.get_vpc(properties['VpcId']) peer_vpc = ec2_backend.get_vpc(properties['PeerVpcId']) @@ -1543,10 +1550,11 @@ class Subnet(TaggedEC2Resource): self.cidr_block = cidr_block @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] vpc_id = properties['VpcId'] + ec2_backend = ec2_backends[region_name] subnet = ec2_backend.create_subnet( vpc_id=vpc_id, cidr_block=properties['CidrBlock'] @@ -1615,12 +1623,13 @@ class SubnetRouteTableAssociation(object): self.subnet_id = subnet_id @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] route_table_id = properties['RouteTableId'] subnet_id = properties['SubnetId'] + ec2_backend = ec2_backends[region_name] subnet_association = ec2_backend.create_subnet_association( route_table_id=route_table_id, subnet_id=subnet_id, @@ -1649,10 +1658,11 @@ class RouteTable(TaggedEC2Resource): self.routes = {} @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] vpc_id = properties['VpcId'] + ec2_backend = ec2_backends[region_name] route_table = ec2_backend.create_route_table( vpc_id=vpc_id, ) @@ -1781,7 +1791,7 @@ class Route(object): self.vpc_pcx = vpc_pcx @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] gateway_id = properties.get('GatewayId') @@ -1790,6 +1800,7 @@ class Route(object): pcx_id = properties.get('VpcPeeringConnectionId') route_table_id = properties['RouteTableId'] + ec2_backend = ec2_backends[region_name] route_table = ec2_backend.create_route( route_table_id=route_table_id, destination_cidr_block=properties['DestinationCidrBlock'], @@ -1860,7 +1871,8 @@ class InternetGateway(TaggedEC2Resource): self.vpc = None @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): + ec2_backend = ec2_backends[region_name] return ec2_backend.create_internet_gateway() @property @@ -1935,9 +1947,10 @@ class VPCGatewayAttachment(object): self.vpc_id = vpc_id @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + ec2_backend = ec2_backends[region_name] return ec2_backend.create_vpc_gateway_attachment( gateway_id=properties['InternetGatewayId'], vpc_id=properties['VpcId'], @@ -2056,7 +2069,9 @@ class ElasticAddress(object): self.association_id = None @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): + ec2_backend = ec2_backends[region_name] + properties = cloudformation_json.get('Properties') instance_id = None if properties: diff --git a/moto/elb/__init__.py b/moto/elb/__init__.py index fbee07220..e2efcde4d 100644 --- a/moto/elb/__init__.py +++ b/moto/elb/__init__.py @@ -1,3 +1,10 @@ from __future__ import unicode_literals -from .models import elb_backend -mock_elb = elb_backend.decorator +from .models import elb_backends, elb_backend # flake8: noqa +from ..core.models import MockAWS + + +def mock_elb(func=None): + if func: + return MockAWS(elb_backends)(func) + else: + return MockAWS(elb_backends) diff --git a/moto/elb/models.py b/moto/elb/models.py index c0eb4e686..f1d71f959 100644 --- a/moto/elb/models.py +++ b/moto/elb/models.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals + +import boto.ec2.elb from moto.core import BaseBackend @@ -38,9 +40,10 @@ class FakeLoadBalancer(object): self.listeners.append(listener) @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + elb_backend = elb_backends[region_name] new_elb = elb_backend.create_load_balancer( name=properties.get('LoadBalancerName', resource_name), zones=properties.get('AvailabilityZones'), @@ -148,4 +151,9 @@ class ELBBackend(BaseBackend): load_balancer.instance_ids = new_instance_ids return load_balancer -elb_backend = ELBBackend() + +elb_backends = {} +for region in boto.ec2.elb.regions(): + elb_backends[region.name] = ELBBackend() + +elb_backend = elb_backends['us-east-1'] diff --git a/moto/elb/responses.py b/moto/elb/responses.py index 1e777bec0..8895b75f9 100644 --- a/moto/elb/responses.py +++ b/moto/elb/responses.py @@ -2,11 +2,15 @@ from __future__ import unicode_literals from jinja2 import Template from moto.core.responses import BaseResponse -from .models import elb_backend +from .models import elb_backends class ELBResponse(BaseResponse): + @property + def elb_backend(self): + return elb_backends[self.region] + def create_load_balancer(self): """ u'Scheme': [u'internet-facing'], @@ -26,7 +30,7 @@ class ELBResponse(BaseResponse): ports.append([protocol, lb_port, instance_port, ssl_certificate_id]) port_index += 1 - elb_backend.create_load_balancer( + self.elb_backend.create_load_balancer( name=load_balancer_name, zones=availability_zones, ports=ports, @@ -49,14 +53,14 @@ class ELBResponse(BaseResponse): ports.append([protocol, lb_port, instance_port, ssl_certificate_id]) port_index += 1 - elb_backend.create_load_balancer_listeners(name=load_balancer_name, ports=ports) + self.elb_backend.create_load_balancer_listeners(name=load_balancer_name, ports=ports) template = Template(CREATE_LOAD_BALANCER_LISTENERS_TEMPLATE) return template.render() def describe_load_balancers(self): names = [value[0] for key, value in self.querystring.items() if "LoadBalancerNames.member" in key] - load_balancers = elb_backend.describe_load_balancers(names) + load_balancers = self.elb_backend.describe_load_balancers(names) template = Template(DESCRIBE_LOAD_BALANCERS_TEMPLATE) return template.render(load_balancers=load_balancers) @@ -73,18 +77,18 @@ class ELBResponse(BaseResponse): port_index += 1 ports.append(int(port)) - elb_backend.delete_load_balancer_listeners(load_balancer_name, ports) + self.elb_backend.delete_load_balancer_listeners(load_balancer_name, ports) template = Template(DELETE_LOAD_BALANCER_LISTENERS) return template.render() def delete_load_balancer(self): load_balancer_name = self.querystring.get('LoadBalancerName')[0] - elb_backend.delete_load_balancer(load_balancer_name) + self.elb_backend.delete_load_balancer(load_balancer_name) template = Template(DELETE_LOAD_BALANCER_TEMPLATE) return template.render() def configure_health_check(self): - check = elb_backend.configure_health_check( + check = self.elb_backend.configure_health_check( load_balancer_name=self.querystring.get('LoadBalancerName')[0], timeout=self.querystring.get('HealthCheck.Timeout')[0], healthy_threshold=self.querystring.get('HealthCheck.HealthyThreshold')[0], @@ -99,7 +103,7 @@ class ELBResponse(BaseResponse): load_balancer_name = self.querystring.get('LoadBalancerName')[0] instance_ids = [value[0] for key, value in self.querystring.items() if "Instances.member" in key] template = Template(REGISTER_INSTANCES_TEMPLATE) - load_balancer = elb_backend.register_instances(load_balancer_name, instance_ids) + load_balancer = self.elb_backend.register_instances(load_balancer_name, instance_ids) return template.render(load_balancer=load_balancer) def set_load_balancer_listener_sslcertificate(self): @@ -107,7 +111,7 @@ class ELBResponse(BaseResponse): ssl_certificate_id = self.querystring['SSLCertificateId'][0] lb_port = self.querystring['LoadBalancerPort'][0] - elb_backend.set_load_balancer_listener_sslcertificate(load_balancer_name, lb_port, ssl_certificate_id) + self.elb_backend.set_load_balancer_listener_sslcertificate(load_balancer_name, lb_port, ssl_certificate_id) template = Template(SET_LOAD_BALANCER_SSL_CERTIFICATE) return template.render() @@ -116,7 +120,7 @@ class ELBResponse(BaseResponse): load_balancer_name = self.querystring.get('LoadBalancerName')[0] instance_ids = [value[0] for key, value in self.querystring.items() if "Instances.member" in key] template = Template(DEREGISTER_INSTANCES_TEMPLATE) - load_balancer = elb_backend.deregister_instances(load_balancer_name, instance_ids) + load_balancer = self.elb_backend.deregister_instances(load_balancer_name, instance_ids) return template.render(load_balancer=load_balancer) CREATE_LOAD_BALANCER_TEMPLATE = """ diff --git a/moto/iam/models.py b/moto/iam/models.py index 7c4f2ec8f..a9bd4eb71 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -16,7 +16,7 @@ class Role(object): self.policies = policies @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] return iam_backend.create_role( @@ -45,7 +45,7 @@ class InstanceProfile(object): self.roles = roles if roles else [] @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] role_ids = properties['Roles'] diff --git a/moto/sqs/__init__.py b/moto/sqs/__init__.py index 2b934b6c7..18d6caa27 100644 --- a/moto/sqs/__init__.py +++ b/moto/sqs/__init__.py @@ -1,3 +1,10 @@ from __future__ import unicode_literals -from .models import sqs_backend -mock_sqs = sqs_backend.decorator +from .models import sqs_backends, sqs_backend # flake8: noqa +from ..core.models import MockAWS + + +def mock_sqs(func=None): + if func: + return MockAWS(sqs_backends)(func) + else: + return MockAWS(sqs_backends) diff --git a/moto/sqs/models.py b/moto/sqs/models.py index cbcb61892..a31e4b046 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -5,6 +5,7 @@ import time import re from xml.sax.saxutils import escape +import boto.sqs from moto.core import BaseBackend from moto.core.utils import camelcase_to_underscores, get_random_message_id @@ -120,9 +121,10 @@ class Queue(object): self.receive_message_wait_time_seconds = 0 @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): properties = cloudformation_json['Properties'] + sqs_backend = sqs_backends[region_name] return sqs_backend.create_queue( name=properties['QueueName'], visibility_timeout=properties.get('VisibilityTimeout'), @@ -272,4 +274,8 @@ class SQSBackend(BaseBackend): return raise ReceiptHandleIsInvalid -sqs_backend = SQSBackend() +sqs_backends = {} +for region in boto.sqs.regions(): + sqs_backends[region.name] = SQSBackend() + +sqs_backend = sqs_backends['us-east-1'] diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py index 71d68c72f..a151d62ca 100644 --- a/moto/sqs/responses.py +++ b/moto/sqs/responses.py @@ -4,7 +4,7 @@ from jinja2 import Template from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores from .utils import parse_message_attributes -from .models import sqs_backend +from .models import sqs_backends from .exceptions import ( MessageAttributesInvalid, MessageNotInflight, @@ -16,19 +16,23 @@ MAXIMUM_VISIBILTY_TIMEOUT = 43200 class QueuesResponse(BaseResponse): + @property + def sqs_backend(self): + return sqs_backends[self.region] + def create_queue(self): visibility_timeout = None if 'Attribute.1.Name' in self.querystring and self.querystring.get('Attribute.1.Name')[0] == 'VisibilityTimeout': visibility_timeout = self.querystring.get("Attribute.1.Value")[0] queue_name = self.querystring.get("QueueName")[0] - queue = sqs_backend.create_queue(queue_name, visibility_timeout=visibility_timeout) + queue = self.sqs_backend.create_queue(queue_name, visibility_timeout=visibility_timeout) template = Template(CREATE_QUEUE_RESPONSE) return template.render(queue=queue) def get_queue_url(self): queue_name = self.querystring.get("QueueName")[0] - queue = sqs_backend.get_queue(queue_name) + queue = self.sqs_backend.get_queue(queue_name) if queue: template = Template(GET_QUEUE_URL_RESPONSE) return template.render(queue=queue) @@ -37,12 +41,17 @@ class QueuesResponse(BaseResponse): def list_queues(self): queue_name_prefix = self.querystring.get("QueueNamePrefix", [None])[0] - queues = sqs_backend.list_queues(queue_name_prefix) + queues = self.sqs_backend.list_queues(queue_name_prefix) template = Template(LIST_QUEUES_RESPONSE) return template.render(queues=queues) class QueueResponse(BaseResponse): + + @property + def sqs_backend(self): + return sqs_backends[self.region] + def change_message_visibility(self): queue_name = self.path.split("/")[-1] receipt_handle = self.querystring.get("ReceiptHandle")[0] @@ -54,7 +63,7 @@ class QueueResponse(BaseResponse): ), dict(status=400) try: - sqs_backend.change_message_visibility( + self.sqs_backend.change_message_visibility( queue_name=queue_name, receipt_handle=receipt_handle, visibility_timeout=visibility_timeout @@ -67,7 +76,7 @@ class QueueResponse(BaseResponse): def get_queue_attributes(self): queue_name = self.path.split("/")[-1] - queue = sqs_backend.get_queue(queue_name) + queue = self.sqs_backend.get_queue(queue_name) template = Template(GET_QUEUE_ATTRIBUTES_RESPONSE) return template.render(queue=queue) @@ -75,12 +84,12 @@ class QueueResponse(BaseResponse): queue_name = self.path.split("/")[-1] key = camelcase_to_underscores(self.querystring.get('Attribute.Name')[0]) value = self.querystring.get('Attribute.Value')[0] - sqs_backend.set_queue_attribute(queue_name, key, value) + self.sqs_backend.set_queue_attribute(queue_name, key, value) return SET_QUEUE_ATTRIBUTE_RESPONSE def delete_queue(self): queue_name = self.path.split("/")[-1] - queue = sqs_backend.delete_queue(queue_name) + queue = self.sqs_backend.delete_queue(queue_name) if not queue: return "A queue with name {0} does not exist".format(queue_name), dict(status=404) template = Template(DELETE_QUEUE_RESPONSE) @@ -101,7 +110,7 @@ class QueueResponse(BaseResponse): return e.description, dict(status=e.status_code) queue_name = self.path.split("/")[-1] - message = sqs_backend.send_message( + message = self.sqs_backend.send_message( queue_name, message, message_attributes=message_attributes, @@ -137,7 +146,7 @@ class QueueResponse(BaseResponse): message_user_id = self.querystring.get(message_user_id_key)[0] delay_key = 'SendMessageBatchRequestEntry.{0}.DelaySeconds'.format(index) delay_seconds = self.querystring.get(delay_key, [None])[0] - message = sqs_backend.send_message(queue_name, message_body[0], delay_seconds=delay_seconds) + message = self.sqs_backend.send_message(queue_name, message_body[0], delay_seconds=delay_seconds) message.user_id = message_user_id message_attributes = parse_message_attributes(self.querystring, base='SendMessageBatchRequestEntry.{0}.'.format(index), value_namespace='') @@ -153,7 +162,7 @@ class QueueResponse(BaseResponse): def delete_message(self): queue_name = self.path.split("/")[-1] receipt_handle = self.querystring.get("ReceiptHandle")[0] - sqs_backend.delete_message(queue_name, receipt_handle) + self.sqs_backend.delete_message(queue_name, receipt_handle) template = Template(DELETE_MESSAGE_RESPONSE) return template.render() @@ -178,7 +187,7 @@ class QueueResponse(BaseResponse): # Found all messages break - sqs_backend.delete_message(queue_name, receipt_handle[0]) + self.sqs_backend.delete_message(queue_name, receipt_handle[0]) message_user_id_key = 'DeleteMessageBatchRequestEntry.{0}.Id'.format(index) message_user_id = self.querystring.get(message_user_id_key)[0] @@ -190,7 +199,7 @@ class QueueResponse(BaseResponse): def receive_message(self): queue_name = self.path.split("/")[-1] message_count = int(self.querystring.get("MaxNumberOfMessages")[0]) - messages = sqs_backend.receive_messages(queue_name, message_count) + messages = self.sqs_backend.receive_messages(queue_name, message_count) template = Template(RECEIVE_MESSAGE_RESPONSE) output = template.render(messages=messages) return output diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 4ab7b2062..f97542391 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -1,9 +1,12 @@ from __future__ import unicode_literals + import json + import boto +import boto.cloudformation import sure # noqa # Ensure 'assert_raises' context manager support for Python 2.6 -import tests.backport_assert_raises +import tests.backport_assert_raises # noqa from nose.tools import assert_raises from moto import mock_cloudformation @@ -38,6 +41,18 @@ def test_create_stack(): stack.get_template().should.equal(dummy_template) +@mock_cloudformation +def test_creating_stacks_across_regions(): + west1_conn = boto.cloudformation.connect_to_region("us-west-1") + west1_conn.create_stack("test_stack", template_body=dummy_template_json) + + west2_conn = boto.cloudformation.connect_to_region("us-west-2") + west2_conn.create_stack("test_stack", template_body=dummy_template_json) + + list(west1_conn.describe_stacks()).should.have.length_of(1) + list(west2_conn.describe_stacks()).should.have.length_of(1) + + @mock_cloudformation def test_create_stack_with_notification_arn(): conn = boto.connect_cloudformation() diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 3cda40928..7fb06cf20 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals import json import boto +import boto.cloudformation +import boto.ec2 import sure # noqa from moto import ( @@ -464,13 +466,13 @@ def test_iam_roles(): def test_single_instance_with_ebs_volume(): template_json = json.dumps(single_instance_with_ebs_volume.template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "test_stack", template_body=template_json, ) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") reservation = ec2_conn.get_all_instances()[0] ec2_instance = reservation.instances[0] @@ -490,10 +492,7 @@ def test_classic_eip(): template_json = json.dumps(ec2_classic_eip.template) conn = boto.connect_cloudformation() - conn.create_stack( - "test_stack", - template_body=template_json, - ) + conn.create_stack("test_stack", template_body=template_json) ec2_conn = boto.connect_ec2() eip = ec2_conn.get_all_addresses()[0] @@ -509,10 +508,7 @@ def test_vpc_eip(): template_json = json.dumps(vpc_eip.template) conn = boto.connect_cloudformation() - conn.create_stack( - "test_stack", - template_body=template_json, - ) + conn.create_stack("test_stack", template_body=template_json) ec2_conn = boto.connect_ec2() eip = ec2_conn.get_all_addresses()[0] @@ -528,10 +524,7 @@ def test_fn_join(): template_json = json.dumps(fn_join.template) conn = boto.connect_cloudformation() - conn.create_stack( - "test_stack", - template_body=template_json, - ) + conn.create_stack("test_stack", template_body=template_json) ec2_conn = boto.connect_ec2() eip = ec2_conn.get_all_addresses()[0] diff --git a/tests/test_cloudformation/test_stack_parsing.py b/tests/test_cloudformation/test_stack_parsing.py index 286d5de74..548c6fd66 100644 --- a/tests/test_cloudformation/test_stack_parsing.py +++ b/tests/test_cloudformation/test_stack_parsing.py @@ -82,7 +82,7 @@ def test_parse_stack_resources(): stack_id="test_id", name="test_stack", template=dummy_template_json, - ) + region_name='us-west-1') stack.resource_map.should.have.length_of(1) list(stack.resource_map.keys())[0].should.equal('Queue') @@ -101,7 +101,8 @@ def test_parse_stack_with_name_type_resource(): stack = FakeStack( stack_id="test_id", name="test_stack", - template=name_type_template_json) + template=name_type_template_json, + region_name='us-west-1') stack.resource_map.should.have.length_of(1) list(stack.resource_map.keys())[0].should.equal('Queue') @@ -113,7 +114,8 @@ def test_parse_stack_with_outputs(): stack = FakeStack( stack_id="test_id", name="test_stack", - template=output_type_template_json) + template=output_type_template_json, + region_name='us-west-1') stack.output_map.should.have.length_of(1) list(stack.output_map.keys())[0].should.equal('Output1') @@ -126,7 +128,8 @@ def test_parse_stack_with_get_attribute_outputs(): stack = FakeStack( stack_id="test_id", name="test_stack", - template=get_attribute_outputs_template_json) + template=get_attribute_outputs_template_json, + region_name='us-west-1') stack.output_map.should.have.length_of(1) list(stack.output_map.keys())[0].should.equal('Output1') @@ -137,4 +140,4 @@ def test_parse_stack_with_get_attribute_outputs(): def test_parse_stack_with_bad_get_attribute_outputs(): FakeStack.when.called_with( - "test_id", "test_stack", bad_output_template_json).should.throw(BotoServerError) \ No newline at end of file + "test_id", "test_stack", bad_output_template_json, "us-west-1").should.throw(BotoServerError) From 2a61ef6f91de744afb34d717763ced526adfcf53 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 13:50:58 -0500 Subject: [PATCH 14/21] Cleanup some tests to work across regions. cc #232 --- .../test_cloudformation_stack_integration.py | 50 ++++++++++--------- tests/test_ec2/test_elastic_block_store.py | 2 +- .../test_elastic_network_interfaces.py | 6 ++- tests/test_ec2/test_spot_instances.py | 4 +- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 7fb06cf20..494fb0dfe 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -4,6 +4,10 @@ import json import boto import boto.cloudformation import boto.ec2 +import boto.ec2.autoscale +import boto.ec2.elb +import boto.iam +import boto.vpc import sure # noqa from moto import ( @@ -40,7 +44,7 @@ def test_stack_sqs_integration(): } sqs_template_json = json.dumps(sqs_template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "test_stack", template_body=sqs_template_json, @@ -70,13 +74,13 @@ def test_stack_ec2_integration(): } ec2_template_json = json.dumps(ec2_template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "ec2_stack", template_body=ec2_template_json, ) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") reservation = ec2_conn.get_all_instances()[0] ec2_instance = reservation.instances[0] @@ -113,16 +117,16 @@ def test_stack_elb_integration_with_attached_ec2_instances(): } elb_template_json = json.dumps(elb_template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "elb_stack", template_body=elb_template_json, ) - elb_conn = boto.connect_elb() + elb_conn = boto.ec2.elb.connect_to_region("us-west-1") load_balancer = elb_conn.get_all_load_balancers()[0] - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") reservation = ec2_conn.get_all_instances()[0] ec2_instance = reservation.instances[0] instance_id = ec2_instance.id @@ -185,13 +189,13 @@ def test_stack_security_groups(): } security_group_template_json = json.dumps(security_group_template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "security_group_stack", template_body=security_group_template_json, ) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") security_groups = ec2_conn.get_all_security_groups() for group in security_groups: if "InstanceSecurityGroup" in group.name: @@ -268,13 +272,13 @@ def test_autoscaling_group_with_elb(): web_setup_template_json = json.dumps(web_setup_template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "web_stack", template_body=web_setup_template_json, ) - autoscale_conn = boto.connect_autoscale() + autoscale_conn = boto.ec2.autoscale.connect_to_region("us-west-1") autoscale_group = autoscale_conn.get_all_groups()[0] autoscale_group.launch_config_name.should.contain("my-launch-config") autoscale_group.load_balancers[0].should.equal('my-elb') @@ -283,7 +287,7 @@ def test_autoscaling_group_with_elb(): autoscale_conn.get_all_launch_configurations().should.have.length_of(1) # Confirm the ELB was actually created - elb_conn = boto.connect_elb() + elb_conn = boto.ec2.elb.connect_to_region("us-west-1") elb_conn.get_all_load_balancers().should.have.length_of(1) stack = conn.describe_stacks()[0] @@ -303,13 +307,13 @@ def test_autoscaling_group_with_elb(): def test_vpc_single_instance_in_subnet(): template_json = json.dumps(vpc_single_instance_in_subnet.template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "test_stack", template_body=template_json, ) - vpc_conn = boto.connect_vpc() + vpc_conn = boto.vpc.connect_to_region("us-west-1") vpc = vpc_conn.get_all_vpcs()[0] vpc.cidr_block.should.equal("10.0.0.0/16") @@ -319,7 +323,7 @@ def test_vpc_single_instance_in_subnet(): subnet = vpc_conn.get_all_subnets()[0] subnet.vpc_id.should.equal(vpc.id) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") reservation = ec2_conn.get_all_instances()[0] instance = reservation.instances[0] # Check that the EIP is attached the the EC2 instance @@ -428,13 +432,13 @@ def test_iam_roles(): } iam_template_json = json.dumps(iam_template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "test_stack", template_body=iam_template_json, ) - iam_conn = boto.connect_iam() + iam_conn = boto.iam.connect_to_region("us-west-1") role_result = iam_conn.list_roles()['list_roles_response']['list_roles_result']['roles'][0] role = iam_conn.get_role(role_result.role_name) @@ -448,7 +452,7 @@ def test_iam_roles(): instance_profile.path.should.equal("my-path") instance_profile.role_id.should.equal(role.role_id) - autoscale_conn = boto.connect_autoscale() + autoscale_conn = boto.ec2.autoscale.connect_to_region("us-west-1") launch_config = autoscale_conn.get_all_launch_configurations()[0] launch_config.instance_profile_name.should.contain("my-instance-profile") @@ -491,9 +495,9 @@ def test_single_instance_with_ebs_volume(): def test_classic_eip(): template_json = json.dumps(ec2_classic_eip.template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack("test_stack", template_body=template_json) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") eip = ec2_conn.get_all_addresses()[0] stack = conn.describe_stacks()[0] @@ -507,9 +511,9 @@ def test_classic_eip(): def test_vpc_eip(): template_json = json.dumps(vpc_eip.template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack("test_stack", template_body=template_json) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") eip = ec2_conn.get_all_addresses()[0] stack = conn.describe_stacks()[0] @@ -523,9 +527,9 @@ def test_vpc_eip(): def test_fn_join(): template_json = json.dumps(fn_join.template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack("test_stack", template_body=template_json) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") eip = ec2_conn.get_all_addresses()[0] stack = conn.describe_stacks()[0] diff --git a/tests/test_ec2/test_elastic_block_store.py b/tests/test_ec2/test_elastic_block_store.py index 556604c22..50fe6b7ad 100644 --- a/tests/test_ec2/test_elastic_block_store.py +++ b/tests/test_ec2/test_elastic_block_store.py @@ -190,7 +190,7 @@ def test_modify_attribute_blockDeviceMapping(): [0] https://github.com/spulec/moto/issues/160 """ - conn = boto.connect_ec2('the_key', 'the_secret') + conn = boto.ec2.connect_to_region("us-east-1") reservation = conn.run_instances('ami-1234abcd') diff --git a/tests/test_ec2/test_elastic_network_interfaces.py b/tests/test_ec2/test_elastic_network_interfaces.py index 500d6a8a6..f60c6c5aa 100644 --- a/tests/test_ec2/test_elastic_network_interfaces.py +++ b/tests/test_ec2/test_elastic_network_interfaces.py @@ -4,6 +4,8 @@ import tests.backport_assert_raises from nose.tools import assert_raises import boto +import boto.cloudformation +import boto.ec2 from boto.exception import EC2ResponseError import sure # noqa @@ -151,12 +153,12 @@ def test_elastic_network_interfaces_filtering(): def test_elastic_network_interfaces_cloudformation(): template = vpc_eni.template template_json = json.dumps(template) - conn = boto.connect_cloudformation() + conn = boto.cloudformation.connect_to_region("us-west-1") conn.create_stack( "test_stack", template_body=template_json, ) - ec2_conn = boto.connect_ec2() + ec2_conn = boto.ec2.connect_to_region("us-west-1") eni = ec2_conn.get_all_network_interfaces()[0] stack = conn.describe_stacks()[0] diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py index 5db120e71..d914b663b 100644 --- a/tests/test_ec2/test_spot_instances.py +++ b/tests/test_ec2/test_spot_instances.py @@ -106,7 +106,7 @@ def test_request_spot_instances_fulfilled(): """ Test that moto correctly fullfills a spot instance request """ - conn = boto.connect_ec2() + conn = boto.ec2.connect_to_region("us-east-1") request = conn.request_spot_instances( price=0.5, image_id='ami-abcd1234', @@ -184,7 +184,7 @@ def test_get_all_spot_instance_requests_filtering(): @mock_ec2 def test_request_spot_instances_setting_instance_id(): - conn = boto.connect_ec2() + conn = boto.ec2.connect_to_region("us-east-1") request = conn.request_spot_instances( price=0.5, image_id='ami-abcd1234') From 1f8253a1a1c3301806c47f9c6eeca86f5ec08edd Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 15 Nov 2014 14:21:58 -0500 Subject: [PATCH 15/21] Cleanup the default region backends. --- moto/autoscaling/__init__.py | 4 +++- moto/autoscaling/models.py | 3 --- moto/cloudformation/__init__.py | 4 +++- moto/cloudformation/models.py | 2 -- moto/cloudformation/parsing.py | 2 +- moto/ec2/__init__.py | 4 +++- moto/ec2/models.py | 2 -- moto/elb/__init__.py | 4 +++- moto/elb/models.py | 2 -- moto/sns/models.py | 2 +- moto/sqs/__init__.py | 4 +++- moto/sqs/models.py | 2 -- 12 files changed, 17 insertions(+), 18 deletions(-) diff --git a/moto/autoscaling/__init__.py b/moto/autoscaling/__init__.py index 64e8e9b18..cefcc3cf7 100644 --- a/moto/autoscaling/__init__.py +++ b/moto/autoscaling/__init__.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals -from .models import autoscaling_backend, autoscaling_backends # flake8: noqa +from .models import autoscaling_backends from ..core.models import MockAWS +autoscaling_backend = autoscaling_backends['us-east-1'] + def mock_autoscaling(func=None): if func: diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index 09110b016..edd580dea 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -359,6 +359,3 @@ class AutoScalingBackend(BaseBackend): autoscaling_backends = {} for region, ec2_backend in ec2_backends.items(): autoscaling_backends[region] = AutoScalingBackend(ec2_backend) - -autoscaling_backend = autoscaling_backends['us-east-1'] -default_autoscaling_backend = autoscaling_backend diff --git a/moto/cloudformation/__init__.py b/moto/cloudformation/__init__.py index 1d7ff041f..98587fc41 100644 --- a/moto/cloudformation/__init__.py +++ b/moto/cloudformation/__init__.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals -from .models import cloudformation_backends, cloudformation_backend # flake8: noqa +from .models import cloudformation_backends from ..core.models import MockAWS +cloudformation_backend = cloudformation_backends['us-east-1'] + def mock_cloudformation(func=None): if func: diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index aeb130e23..229380796 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -101,5 +101,3 @@ class CloudFormationBackend(BaseBackend): cloudformation_backends = {} for region in boto.cloudformation.regions(): cloudformation_backends[region.name] = CloudFormationBackend() - -cloudformation_backend = cloudformation_backends['us-east-1'] diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 07bf72e35..f29c77003 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -215,7 +215,7 @@ class ResourceMap(collections.Mapping): self[resource] if isinstance(self[resource], ec2_models.TaggedEC2Resource): tags['aws:cloudformation:logical-id'] = resource - ec2_models.ec2_backend.create_tags([self[resource].physical_resource_id], tags) + ec2_models.ec2_backends[self._region_name].create_tags([self[resource].physical_resource_id], tags) class OutputMap(collections.Mapping): diff --git a/moto/ec2/__init__.py b/moto/ec2/__init__.py index 53547cafe..2b1cafd88 100644 --- a/moto/ec2/__init__.py +++ b/moto/ec2/__init__.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals -from .models import ec2_backend, ec2_backends # flake8: noqa +from .models import ec2_backends from ..core.models import MockAWS +ec2_backend = ec2_backends['us-east-1'] + def mock_ec2(func=None): if func: diff --git a/moto/ec2/models.py b/moto/ec2/models.py index cf380d3fa..32f310640 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -2337,5 +2337,3 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, ec2_backends = {} for region in boto.ec2.regions(): ec2_backends[region.name] = EC2Backend() - -ec2_backend = ec2_backends['us-east-1'] diff --git a/moto/elb/__init__.py b/moto/elb/__init__.py index e2efcde4d..fd53c8587 100644 --- a/moto/elb/__init__.py +++ b/moto/elb/__init__.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals -from .models import elb_backends, elb_backend # flake8: noqa +from .models import elb_backends from ..core.models import MockAWS +elb_backend = elb_backends['us-east-1'] + def mock_elb(func=None): if func: diff --git a/moto/elb/models.py b/moto/elb/models.py index f1d71f959..94914a8e7 100644 --- a/moto/elb/models.py +++ b/moto/elb/models.py @@ -155,5 +155,3 @@ class ELBBackend(BaseBackend): elb_backends = {} for region in boto.ec2.elb.regions(): elb_backends[region.name] = ELBBackend() - -elb_backend = elb_backends['us-east-1'] diff --git a/moto/sns/models.py b/moto/sns/models.py index 9b11d9530..886007aee 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -6,7 +6,7 @@ import six from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime -from moto.sqs.models import sqs_backend +from moto.sqs import sqs_backend from .utils import make_arn_for_topic, make_arn_for_subscription DEFAULT_ACCOUNT_ID = 123456789012 diff --git a/moto/sqs/__init__.py b/moto/sqs/__init__.py index 18d6caa27..0a9de1a47 100644 --- a/moto/sqs/__init__.py +++ b/moto/sqs/__init__.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals -from .models import sqs_backends, sqs_backend # flake8: noqa +from .models import sqs_backends from ..core.models import MockAWS +sqs_backend = sqs_backends['us-east-1'] + def mock_sqs(func=None): if func: diff --git a/moto/sqs/models.py b/moto/sqs/models.py index a31e4b046..11d91cfb8 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -277,5 +277,3 @@ class SQSBackend(BaseBackend): sqs_backends = {} for region in boto.sqs.regions(): sqs_backends[region.name] = SQSBackend() - -sqs_backend = sqs_backends['us-east-1'] From aa7233a2db9a7fb09089f2d52621ff74453a205b Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 16 Nov 2014 17:57:46 -0500 Subject: [PATCH 16/21] Fixes for multi-region SQS. --- moto/core/responses.py | 7 +++++-- moto/sqs/responses.py | 2 ++ tests/test_sqs/test_sqs.py | 21 +++++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/moto/core/responses.py b/moto/core/responses.py index e48f7df9b..726eac686 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -43,7 +43,8 @@ def _decode_dict(d): class BaseResponse(object): - region = 'us-east-1' + default_region = 'us-east-1' + region_regex = r'\.(.+?)\.amazonaws\.com' def dispatch(self, request, full_url, headers): querystring = {} @@ -76,9 +77,11 @@ class BaseResponse(object): self.path = urlparse(full_url).path self.querystring = querystring self.method = request.method - region = re.search(r'\.(.+?)\.amazonaws\.com', full_url) + region = re.search(self.region_regex, full_url) if region: self.region = region.group(1) + else: + self.region = self.default_region self.headers = dict(request.headers) self.response_headers = headers diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py index a151d62ca..6ab053483 100644 --- a/moto/sqs/responses.py +++ b/moto/sqs/responses.py @@ -16,6 +16,8 @@ MAXIMUM_VISIBILTY_TIMEOUT = 43200 class QueuesResponse(BaseResponse): + region_regex = r'://(.+?)\.queue\.amazonaws\.com' + @property def sqs_backend(self): return sqs_backends[self.region] diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index 7f60bb17c..fcad635d1 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -10,6 +10,7 @@ import time from moto import mock_sqs from tests.helpers import requires_boto_gte + @mock_sqs def test_create_queue(): conn = boto.connect_sqs('the_key', 'the_secret') @@ -21,6 +22,18 @@ def test_create_queue(): all_queues[0].get_timeout().should.equal(60) +@mock_sqs +def test_create_queues_in_multiple_region(): + west1_conn = boto.sqs.connect_to_region("us-west-1") + west1_conn.create_queue("test-queue") + + west2_conn = boto.sqs.connect_to_region("us-west-2") + west2_conn.create_queue("test-queue") + + list(west1_conn.get_all_queues()).should.have.length_of(1) + list(west2_conn.get_all_queues()).should.have.length_of(1) + + @mock_sqs def test_get_queue(): conn = boto.connect_sqs('the_key', 'the_secret') @@ -113,13 +126,13 @@ def test_send_message_with_attributes(): conn = boto.connect_sqs('the_key', 'the_secret') queue = conn.create_queue("test-queue", visibility_timeout=60) queue.set_message_class(RawMessage) - + body = 'this is a test message' message = queue.new_message(body) message_attributes = { - 'test.attribute_name' : {'data_type' : 'String', 'string_value' : 'attribute value'}, - 'test.binary_attribute' : {'data_type' : 'Binary', 'binary_value' : 'binary value'}, - 'test.number_attribute' : {'data_type' : 'Number', 'string_value' : 'string value'} + 'test.attribute_name': {'data_type': 'String', 'string_value': 'attribute value'}, + 'test.binary_attribute': {'data_type': 'Binary', 'binary_value': 'binary value'}, + 'test.number_attribute': {'data_type': 'Number', 'string_value': 'string value'} } message.message_attributes = message_attributes From 2b775aa0759d1fa9affc5b83e0facc68f21e5c71 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 16 Nov 2014 18:00:04 -0500 Subject: [PATCH 17/21] Add multi-region ELB test. --- tests/test_elb/test_elb.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_elb/test_elb.py b/tests/test_elb/test_elb.py index 5e2377cfd..abfa38e5c 100644 --- a/tests/test_elb/test_elb.py +++ b/tests/test_elb/test_elb.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals import boto +import boto.ec2.elb from boto.ec2.elb import HealthCheck import sure # noqa @@ -28,6 +29,21 @@ def test_create_load_balancer(): listener2.protocol.should.equal("TCP") +@mock_elb +def test_create_elb_in_multiple_region(): + zones = ['us-east-1a', 'us-east-1b'] + ports = [(80, 8080, 'http'), (443, 8443, 'tcp')] + + west1_conn = boto.ec2.elb.connect_to_region("us-west-1") + west1_conn.create_load_balancer('my-lb', zones, ports) + + west2_conn = boto.ec2.elb.connect_to_region("us-west-2") + west2_conn.create_load_balancer('my-lb', zones, ports) + + list(west1_conn.get_all_load_balancers()).should.have.length_of(1) + list(west2_conn.get_all_load_balancers()).should.have.length_of(1) + + @mock_elb def test_add_listener(): conn = boto.connect_elb() From 53acdf6c763b87e6ed96553ebbd2dd705e98144d Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 16 Nov 2014 18:35:11 -0500 Subject: [PATCH 18/21] Make SNS multi-region. --- moto/sns/__init__.py | 13 +++++++++++-- moto/sns/models.py | 17 +++++++++++------ moto/sns/responses.py | 26 +++++++++++++++----------- tests/test_sns/test_topics.py | 12 ++++++++++++ 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/moto/sns/__init__.py b/moto/sns/__init__.py index f64c5e133..1aa1a0e3e 100644 --- a/moto/sns/__init__.py +++ b/moto/sns/__init__.py @@ -1,3 +1,12 @@ from __future__ import unicode_literals -from .models import sns_backend -mock_sns = sns_backend.decorator +from .models import sns_backends +from ..core.models import MockAWS + +sns_backend = sns_backends['us-east-1'] + + +def mock_sns(func=None): + if func: + return MockAWS(sns_backends)(func) + else: + return MockAWS(sns_backends) diff --git a/moto/sns/models.py b/moto/sns/models.py index 886007aee..891ccee43 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -1,7 +1,10 @@ from __future__ import unicode_literals + import datetime -import requests import uuid + +import boto.sns +import requests import six from moto.core import BaseBackend @@ -13,8 +16,9 @@ DEFAULT_ACCOUNT_ID = 123456789012 class Topic(object): - def __init__(self, name): + def __init__(self, name, sns_backend): self.name = name + self.sns_backend = sns_backend self.account_id = DEFAULT_ACCOUNT_ID self.display_name = "" self.policy = DEFAULT_TOPIC_POLICY @@ -28,7 +32,7 @@ class Topic(object): def publish(self, message): message_id = six.text_type(uuid.uuid4()) - subscriptions = sns_backend.list_subscriptions(self.arn) + subscriptions = self.sns_backend.list_subscriptions(self.arn) for subscription in subscriptions: subscription.publish(message, message_id) return message_id @@ -76,7 +80,7 @@ class SNSBackend(BaseBackend): self.subscriptions = {} def create_topic(self, name): - topic = Topic(name) + topic = Topic(name, self) self.topics[topic.arn] = topic return topic @@ -114,8 +118,9 @@ class SNSBackend(BaseBackend): message_id = topic.publish(message) return message_id - -sns_backend = SNSBackend() +sns_backends = {} +for region in boto.sns.regions(): + sns_backends[region.name] = SNSBackend() DEFAULT_TOPIC_POLICY = { diff --git a/moto/sns/responses.py b/moto/sns/responses.py index dc3735f6b..19ebdd04c 100644 --- a/moto/sns/responses.py +++ b/moto/sns/responses.py @@ -3,14 +3,18 @@ import json from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores -from .models import sns_backend +from .models import sns_backends class SNSResponse(BaseResponse): + @property + def backend(self): + return sns_backends[self.region] + def create_topic(self): name = self._get_param('Name') - topic = sns_backend.create_topic(name) + topic = self.backend.create_topic(name) return json.dumps({ 'CreateTopicResponse': { @@ -24,7 +28,7 @@ class SNSResponse(BaseResponse): }) def list_topics(self): - topics = sns_backend.list_topics() + topics = self.backend.list_topics() return json.dumps({ 'ListTopicsResponse': { @@ -40,7 +44,7 @@ class SNSResponse(BaseResponse): def delete_topic(self): topic_arn = self._get_param('TopicArn') - sns_backend.delete_topic(topic_arn) + self.backend.delete_topic(topic_arn) return json.dumps({ 'DeleteTopicResponse': { @@ -52,7 +56,7 @@ class SNSResponse(BaseResponse): def get_topic_attributes(self): topic_arn = self._get_param('TopicArn') - topic = sns_backend.get_topic(topic_arn) + topic = self.backend.get_topic(topic_arn) return json.dumps({ "GetTopicAttributesResponse": { @@ -80,7 +84,7 @@ class SNSResponse(BaseResponse): attribute_name = self._get_param('AttributeName') attribute_name = camelcase_to_underscores(attribute_name) attribute_value = self._get_param('AttributeValue') - sns_backend.set_topic_attribute(topic_arn, attribute_name, attribute_value) + self.backend.set_topic_attribute(topic_arn, attribute_name, attribute_value) return json.dumps({ "SetTopicAttributesResponse": { @@ -94,7 +98,7 @@ class SNSResponse(BaseResponse): topic_arn = self._get_param('TopicArn') endpoint = self._get_param('Endpoint') protocol = self._get_param('Protocol') - subscription = sns_backend.subscribe(topic_arn, endpoint, protocol) + subscription = self.backend.subscribe(topic_arn, endpoint, protocol) return json.dumps({ "SubscribeResponse": { @@ -109,7 +113,7 @@ class SNSResponse(BaseResponse): def unsubscribe(self): subscription_arn = self._get_param('SubscriptionArn') - sns_backend.unsubscribe(subscription_arn) + self.backend.unsubscribe(subscription_arn) return json.dumps({ "UnsubscribeResponse": { @@ -120,7 +124,7 @@ class SNSResponse(BaseResponse): }) def list_subscriptions(self): - subscriptions = sns_backend.list_subscriptions() + subscriptions = self.backend.list_subscriptions() return json.dumps({ "ListSubscriptionsResponse": { @@ -142,7 +146,7 @@ class SNSResponse(BaseResponse): def list_subscriptions_by_topic(self): topic_arn = self._get_param('TopicArn') - subscriptions = sns_backend.list_subscriptions(topic_arn) + subscriptions = self.backend.list_subscriptions(topic_arn) return json.dumps({ "ListSubscriptionsByTopicResponse": { @@ -165,7 +169,7 @@ class SNSResponse(BaseResponse): def publish(self): topic_arn = self._get_param('TopicArn') message = self._get_param('Message') - message_id = sns_backend.publish(topic_arn, message) + message_id = self.backend.publish(topic_arn, message) return json.dumps({ "PublishResponse": { diff --git a/tests/test_sns/test_topics.py b/tests/test_sns/test_topics.py index b4129425b..427c8c003 100644 --- a/tests/test_sns/test_topics.py +++ b/tests/test_sns/test_topics.py @@ -27,6 +27,18 @@ def test_create_and_delete_topic(): topics.should.have.length_of(0) +@mock_sns +def test_create_topic_in_multiple_regions(): + west1_conn = boto.sns.connect_to_region("us-west-1") + west1_conn.create_topic("some-topic") + + west2_conn = boto.sns.connect_to_region("us-west-2") + west2_conn.create_topic("some-topic") + + list(west1_conn.get_all_topics()["ListTopicsResponse"]["ListTopicsResult"]["Topics"]).should.have.length_of(1) + list(west2_conn.get_all_topics()["ListTopicsResponse"]["ListTopicsResult"]["Topics"]).should.have.length_of(1) + + @mock_sns def test_topic_attributes(): conn = boto.connect_sns() From 16660ab72bf748222d662fca6cc7ffec1ac9ee33 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 16 Nov 2014 18:42:53 -0500 Subject: [PATCH 19/21] Allow message passing to SQS queues in different regions from SNS. --- moto/sns/models.py | 5 +++-- moto/sqs/responses.py | 5 ++++- tests/test_sns/test_publishing.py | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/moto/sns/models.py b/moto/sns/models.py index 891ccee43..60864bc21 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -9,7 +9,7 @@ import six from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime -from moto.sqs import sqs_backend +from moto.sqs import sqs_backends from .utils import make_arn_for_topic, make_arn_for_subscription DEFAULT_ACCOUNT_ID = 123456789012 @@ -54,7 +54,8 @@ class Subscription(object): def publish(self, message, message_id): if self.protocol == 'sqs': queue_name = self.endpoint.split(":")[-1] - sqs_backend.send_message(queue_name, message) + region = self.endpoint.split(":")[3] + sqs_backends[region].send_message(queue_name, message) elif self.protocol in ['http', 'https']: post_data = self.get_post_data(message, message_id) requests.post(self.endpoint, data=post_data) diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py index 6ab053483..3d0cef2ce 100644 --- a/moto/sqs/responses.py +++ b/moto/sqs/responses.py @@ -12,11 +12,12 @@ from .exceptions import ( ) MAXIMUM_VISIBILTY_TIMEOUT = 43200 +SQS_REGION_REGEX = r'://(.+?)\.queue\.amazonaws\.com' class QueuesResponse(BaseResponse): - region_regex = r'://(.+?)\.queue\.amazonaws\.com' + region_regex = SQS_REGION_REGEX @property def sqs_backend(self): @@ -50,6 +51,8 @@ class QueuesResponse(BaseResponse): class QueueResponse(BaseResponse): + region_regex = SQS_REGION_REGEX + @property def sqs_backend(self): return sqs_backends[self.region] diff --git a/tests/test_sns/test_publishing.py b/tests/test_sns/test_publishing.py index 315e44cba..7566f8d4d 100644 --- a/tests/test_sns/test_publishing.py +++ b/tests/test_sns/test_publishing.py @@ -29,6 +29,26 @@ def test_publish_to_sqs(): message.get_body().should.equal('my message') +@mock_sqs +@mock_sns +def test_publish_to_sqs_in_different_region(): + conn = boto.sns.connect_to_region("us-west-1") + conn.create_topic("some-topic") + topics_json = conn.get_all_topics() + topic_arn = topics_json["ListTopicsResponse"]["ListTopicsResult"]["Topics"][0]['TopicArn'] + + sqs_conn = boto.sqs.connect_to_region("us-west-2") + sqs_conn.create_queue("test-queue") + + conn.subscribe(topic_arn, "sqs", "arn:aws:sqs:us-west-2:123456789012:test-queue") + + conn.publish(topic=topic_arn, message="my message") + + queue = sqs_conn.get_queue("test-queue") + message = queue.read(1) + message.get_body().should.equal('my message') + + @freeze_time("2013-01-01") @mock_sns def test_publish_to_http(): From 828ed13bde08f3c958395110f4e25e1782c168f5 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 16 Nov 2014 19:05:28 -0500 Subject: [PATCH 20/21] Allow for class-based decorator. Closes #157 --- moto/core/models.py | 24 ++++++++++++++++++++++++ tests/test_core/test_decorator_calls.py | 13 ++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/moto/core/models.py b/moto/core/models.py index 720d98c95..72b2cb512 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals + import functools +import inspect import re from httpretty import HTTPretty @@ -17,6 +19,8 @@ class MockAWS(object): HTTPretty.reset() def __call__(self, func): + if inspect.isclass(func): + return self.decorate_class(func) return self.decorate_callable(func) def __enter__(self): @@ -67,6 +71,26 @@ class MockAWS(object): wrapper.__wrapped__ = func return wrapper + def decorate_class(self, klass): + for attr in dir(klass): + if attr.startswith("_"): + continue + + attr_value = getattr(klass, attr) + if not hasattr(attr_value, "__call__"): + continue + + # Check if this is a classmethod. If so, skip patching + if inspect.ismethod(attr_value) and attr_value.__self__ is klass: + continue + + try: + setattr(klass, attr, self(attr_value)) + except TypeError: + # Sometimes we can't set this for built-in types + continue + return klass + class Model(type): def __new__(self, clsname, bases, namespace): diff --git a/tests/test_core/test_decorator_calls.py b/tests/test_core/test_decorator_calls.py index 2688abc41..5360061c8 100644 --- a/tests/test_core/test_decorator_calls.py +++ b/tests/test_core/test_decorator_calls.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import boto from boto.exception import EC2ResponseError import sure # noqa -import tests.backport_assert_raises +import tests.backport_assert_raises # noqa from nose.tools import assert_raises from moto import mock_ec2 @@ -57,3 +57,14 @@ def test_decorater_wrapped_gets_set(): Moto decorator's __wrapped__ should get set to the tests function """ test_decorater_wrapped_gets_set.__wrapped__.__name__.should.equal('test_decorater_wrapped_gets_set') + + +@mock_ec2 +class Tester(object): + def test_the_class(self): + conn = boto.connect_ec2() + list(conn.get_all_instances()).should.have.length_of(0) + + def test_still_the_same(self): + conn = boto.connect_ec2() + list(conn.get_all_instances()).should.have.length_of(0) From 470e9d48e9c8524a0e2c27bd4f1bb98882710173 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 16 Nov 2014 19:25:57 -0500 Subject: [PATCH 21/21] 0.3.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5cd72dea7..f894c5ddd 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ if sys.version_info < (2, 7): setup( name='moto', - version='0.3.8', + version='0.3.9', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec',