From 4fe66f521d61b81c6873a883a3e526fa4845d9b1 Mon Sep 17 00:00:00 2001 From: Jessie Nadler Date: Thu, 3 Oct 2019 15:10:24 -0400 Subject: [PATCH 1/3] Use region to create and describe ECS resources --- moto/ecs/models.py | 43 +++++++++++++++++++------------- tests/test_ecs/test_ecs_boto3.py | 6 ++--- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/moto/ecs/models.py b/moto/ecs/models.py index 863cfc49e..db09ec9e1 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -44,15 +44,17 @@ class BaseObject(BaseModel): class Cluster(BaseObject): - def __init__(self, cluster_name): + def __init__(self, cluster_name, region_name): self.active_services_count = 0 - self.arn = 'arn:aws:ecs:us-east-1:012345678910:cluster/{0}'.format( + self.arn = 'arn:aws:ecs:{0}:012345678910:cluster/{1}'.format( + region_name, cluster_name) self.name = cluster_name self.pending_tasks_count = 0 self.registered_container_instances_count = 0 self.running_tasks_count = 0 self.status = 'ACTIVE' + self.region_name = region_name @property def physical_resource_id(self): @@ -108,11 +110,11 @@ class Cluster(BaseObject): class TaskDefinition(BaseObject): - def __init__(self, family, revision, container_definitions, volumes=None, tags=None): + def __init__(self, family, revision, container_definitions, region_name, volumes=None, tags=None): self.family = family self.revision = revision - self.arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/{0}:{1}'.format( - family, revision) + self.arn = 'arn:aws:ecs:{0}:012345678910:task-definition/{1}:{2}'.format( + region_name, family, revision) self.container_definitions = container_definitions self.tags = tags if tags is not None else [] if volumes is None: @@ -172,7 +174,8 @@ class Task(BaseObject): def __init__(self, cluster, task_definition, container_instance_arn, resource_requirements, overrides={}, started_by=''): self.cluster_arn = cluster.arn - self.task_arn = 'arn:aws:ecs:us-east-1:012345678910:task/{0}'.format( + self.task_arn = 'arn:aws:ecs:{0}:012345678910:task/{1}'.format( + cluster.region_name, str(uuid.uuid4())) self.container_instance_arn = container_instance_arn self.last_status = 'RUNNING' @@ -194,7 +197,8 @@ class Service(BaseObject): def __init__(self, cluster, service_name, task_definition, desired_count, load_balancers=None, scheduling_strategy=None): self.cluster_arn = cluster.arn - self.arn = 'arn:aws:ecs:us-east-1:012345678910:service/{0}'.format( + self.arn = 'arn:aws:ecs:{0}:012345678910:service/{1}'.format( + cluster.region_name, service_name) self.name = service_name self.status = 'ACTIVE' @@ -273,7 +277,7 @@ class Service(BaseObject): ecs_backend = ecs_backends[region_name] service_name = original_resource.name - if original_resource.cluster_arn != Cluster(cluster_name).arn: + if original_resource.cluster_arn != Cluster(cluster_name, region_name).arn: # TODO: LoadBalancers # TODO: Role ecs_backend.delete_service(cluster_name, service_name) @@ -320,7 +324,8 @@ class ContainerInstance(BaseObject): 'name': 'PORTS_UDP', 'stringSetValue': [], 'type': 'STRINGSET'}] - self.container_instance_arn = "arn:aws:ecs:us-east-1:012345678910:container-instance/{0}".format( + self.container_instance_arn = "arn:aws:ecs:{0}:012345678910:container-instance/{1}".format( + region_name, str(uuid.uuid4())) self.pending_tasks_count = 0 self.remaining_resources = [ @@ -378,9 +383,10 @@ class ContainerInstance(BaseObject): class ClusterFailure(BaseObject): - def __init__(self, reason, cluster_name): + def __init__(self, reason, cluster_name, region_name): self.reason = reason - self.arn = "arn:aws:ecs:us-east-1:012345678910:cluster/{0}".format( + self.arn = "arn:aws:ecs:{0}:012345678910:cluster/{1}".format( + region_name, cluster_name) @property @@ -393,9 +399,10 @@ class ClusterFailure(BaseObject): class ContainerInstanceFailure(BaseObject): - def __init__(self, reason, container_instance_id): + def __init__(self, reason, container_instance_id, region_name): self.reason = reason - self.arn = "arn:aws:ecs:us-east-1:012345678910:container-instance/{0}".format( + self.arn = "arn:aws:ecs:{0}:012345678910:container-instance/{1}".format( + region_name, container_instance_id) @property @@ -438,7 +445,7 @@ class EC2ContainerServiceBackend(BaseBackend): "{0} is not a task_definition".format(task_definition_name)) def create_cluster(self, cluster_name): - cluster = Cluster(cluster_name) + cluster = Cluster(cluster_name, self.region_name) self.clusters[cluster_name] = cluster return cluster @@ -461,7 +468,7 @@ class EC2ContainerServiceBackend(BaseBackend): list_clusters.append( self.clusters[cluster_name].response_object) else: - failures.append(ClusterFailure('MISSING', cluster_name)) + failures.append(ClusterFailure('MISSING', cluster_name, self.region_name)) return list_clusters, failures def delete_cluster(self, cluster_str): @@ -479,7 +486,7 @@ class EC2ContainerServiceBackend(BaseBackend): self.task_definitions[family] = {} revision = 1 task_definition = TaskDefinition( - family, revision, container_definitions, volumes, tags) + family, revision, container_definitions, self.region_name, volumes, tags) self.task_definitions[family][revision] = task_definition return task_definition @@ -792,7 +799,7 @@ class EC2ContainerServiceBackend(BaseBackend): container_instance_objects.append(container_instance) else: failures.append(ContainerInstanceFailure( - 'MISSING', container_instance_id)) + 'MISSING', container_instance_id, self.region_name)) return container_instance_objects, failures @@ -814,7 +821,7 @@ class EC2ContainerServiceBackend(BaseBackend): container_instance.status = status container_instance_objects.append(container_instance) else: - failures.append(ContainerInstanceFailure('MISSING', container_instance_id)) + failures.append(ContainerInstanceFailure('MISSING', container_instance_id, self.region_name)) return container_instance_objects, failures diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py index 9937af26b..5aad6499a 100644 --- a/tests/test_ecs/test_ecs_boto3.py +++ b/tests/test_ecs/test_ecs_boto3.py @@ -34,7 +34,7 @@ def test_create_cluster(): @mock_ecs def test_list_clusters(): - client = boto3.client('ecs', region_name='us-east-1') + client = boto3.client('ecs', region_name='us-east-2') _ = client.create_cluster( clusterName='test_cluster0' ) @@ -43,9 +43,9 @@ def test_list_clusters(): ) response = client.list_clusters() response['clusterArns'].should.contain( - 'arn:aws:ecs:us-east-1:012345678910:cluster/test_cluster0') + 'arn:aws:ecs:us-east-2:012345678910:cluster/test_cluster0') response['clusterArns'].should.contain( - 'arn:aws:ecs:us-east-1:012345678910:cluster/test_cluster1') + 'arn:aws:ecs:us-east-2:012345678910:cluster/test_cluster1') @mock_ecs From 277cec0928dd90244627a9a2b0e86f3dcf642477 Mon Sep 17 00:00:00 2001 From: Jessie Nadler Date: Thu, 3 Oct 2019 15:11:09 -0400 Subject: [PATCH 2/3] Add multi region support for Events --- moto/events/__init__.py | 7 +++--- moto/events/models.py | 28 +++++++++++++++------- moto/events/responses.py | 40 ++++++++++++++++++++------------ tests/test_events/test_events.py | 2 +- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/moto/events/__init__.py b/moto/events/__init__.py index 5c93c59c8..8f2730c84 100644 --- a/moto/events/__init__.py +++ b/moto/events/__init__.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals -from .models import events_backend +from .models import events_backends +from ..core.models import base_decorator -events_backends = {"global": events_backend} -mock_events = events_backend.decorator +events_backend = events_backends['us-east-1'] +mock_events = base_decorator(events_backends) diff --git a/moto/events/models.py b/moto/events/models.py index 2422e0b51..6e4a75d07 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -1,6 +1,7 @@ import os import re import json +import boto3 from moto.core.exceptions import JsonRESTError from moto.core import BaseBackend, BaseModel @@ -8,12 +9,15 @@ from moto.core import BaseBackend, BaseModel class Rule(BaseModel): - def _generate_arn(self, name): - return 'arn:aws:events:us-west-2:111111111111:rule/' + name + def _generate_arn(self, name, region_name): + return 'arn:aws:events:{region_name}:111111111111:rule/{name}'.format( + region_name=region_name, + name=name + ) - def __init__(self, name, **kwargs): + def __init__(self, name, region_name, **kwargs): self.name = name - self.arn = kwargs.get('Arn') or self._generate_arn(name) + self.arn = kwargs.get('Arn') or self._generate_arn(name, region_name) self.event_pattern = kwargs.get('EventPattern') self.schedule_exp = kwargs.get('ScheduleExpression') self.state = kwargs.get('State') or 'ENABLED' @@ -55,15 +59,20 @@ class EventsBackend(BaseBackend): ACCOUNT_ID = re.compile(r'^(\d{1,12}|\*)$') STATEMENT_ID = re.compile(r'^[a-zA-Z0-9-_]{1,64}$') - def __init__(self): + def __init__(self, region_name): self.rules = {} # This array tracks the order in which the rules have been added, since # 2.6 doesn't have OrderedDicts. self.rules_order = [] self.next_tokens = {} - + self.region_name = region_name self.permissions = {} + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + def _get_rule_by_index(self, i): return self.rules.get(self.rules_order[i]) @@ -173,7 +182,7 @@ class EventsBackend(BaseBackend): return return_obj def put_rule(self, name, **kwargs): - rule = Rule(name, **kwargs) + rule = Rule(name, self.region_name, **kwargs) self.rules[rule.name] = rule self.rules_order.append(rule.name) return rule.arn @@ -229,7 +238,7 @@ class EventsBackend(BaseBackend): raise JsonRESTError('ResourceNotFoundException', 'StatementId not found') def describe_event_bus(self): - arn = "arn:aws:events:us-east-1:000000000000:event-bus/default" + arn = "arn:aws:events:{0}:000000000000:event-bus/default".format(self.region_name) statements = [] for statement_id, data in self.permissions.items(): statements.append({ @@ -248,4 +257,5 @@ class EventsBackend(BaseBackend): } -events_backend = EventsBackend() +available_regions = boto3.session.Session().get_available_regions("events") +events_backends = {region: EventsBackend(region) for region in available_regions} diff --git a/moto/events/responses.py b/moto/events/responses.py index f9cb9b5b5..2eb72d342 100644 --- a/moto/events/responses.py +++ b/moto/events/responses.py @@ -2,11 +2,21 @@ import json import re from moto.core.responses import BaseResponse -from moto.events import events_backend +from moto.events import events_backends class EventsHandler(BaseResponse): + @property + def events_backend(self): + """ + Events Backend + + :return: Events Backend object + :rtype: moto.events.models.EventsBackend + """ + return events_backends[self.region] + def _generate_rule_dict(self, rule): return { 'Name': rule.name, @@ -40,7 +50,7 @@ class EventsHandler(BaseResponse): if not name: return self.error('ValidationException', 'Parameter Name is required.') - events_backend.delete_rule(name) + self.events_backend.delete_rule(name) return '', self.response_headers @@ -50,7 +60,7 @@ class EventsHandler(BaseResponse): if not name: return self.error('ValidationException', 'Parameter Name is required.') - rule = events_backend.describe_rule(name) + rule = self.events_backend.describe_rule(name) if not rule: return self.error('ResourceNotFoundException', 'Rule test does not exist.') @@ -64,7 +74,7 @@ class EventsHandler(BaseResponse): if not name: return self.error('ValidationException', 'Parameter Name is required.') - if not events_backend.disable_rule(name): + if not self.events_backend.disable_rule(name): return self.error('ResourceNotFoundException', 'Rule ' + name + ' does not exist.') return '', self.response_headers @@ -75,7 +85,7 @@ class EventsHandler(BaseResponse): if not name: return self.error('ValidationException', 'Parameter Name is required.') - if not events_backend.enable_rule(name): + if not self.events_backend.enable_rule(name): return self.error('ResourceNotFoundException', 'Rule ' + name + ' does not exist.') return '', self.response_headers @@ -91,7 +101,7 @@ class EventsHandler(BaseResponse): if not target_arn: return self.error('ValidationException', 'Parameter TargetArn is required.') - rule_names = events_backend.list_rule_names_by_target( + rule_names = self.events_backend.list_rule_names_by_target( target_arn, next_token, limit) return json.dumps(rule_names), self.response_headers @@ -101,7 +111,7 @@ class EventsHandler(BaseResponse): next_token = self._get_param('NextToken') limit = self._get_param('Limit') - rules = events_backend.list_rules(prefix, next_token, limit) + rules = self.events_backend.list_rules(prefix, next_token, limit) rules_obj = {'Rules': []} for rule in rules['Rules']: @@ -121,7 +131,7 @@ class EventsHandler(BaseResponse): return self.error('ValidationException', 'Parameter Rule is required.') try: - targets = events_backend.list_targets_by_rule( + targets = self.events_backend.list_targets_by_rule( rule_name, next_token, limit) except KeyError: return self.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.') @@ -131,7 +141,7 @@ class EventsHandler(BaseResponse): def put_events(self): events = self._get_param('Entries') - failed_entries = events_backend.put_events(events) + failed_entries = self.events_backend.put_events(events) if failed_entries: return json.dumps({ @@ -165,7 +175,7 @@ class EventsHandler(BaseResponse): re.match('^rate\(\d*\s(minute|minutes|hour|hours|day|days)\)', sched_exp)): return self.error('ValidationException', 'Parameter ScheduleExpression is not valid.') - rule_arn = events_backend.put_rule( + rule_arn = self.events_backend.put_rule( name, ScheduleExpression=sched_exp, EventPattern=event_pattern, @@ -186,7 +196,7 @@ class EventsHandler(BaseResponse): if not targets: return self.error('ValidationException', 'Parameter Targets is required.') - if not events_backend.put_targets(rule_name, targets): + if not self.events_backend.put_targets(rule_name, targets): return self.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.') return '', self.response_headers @@ -201,7 +211,7 @@ class EventsHandler(BaseResponse): if not ids: return self.error('ValidationException', 'Parameter Ids is required.') - if not events_backend.remove_targets(rule_name, ids): + if not self.events_backend.remove_targets(rule_name, ids): return self.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.') return '', self.response_headers @@ -214,16 +224,16 @@ class EventsHandler(BaseResponse): principal = self._get_param('Principal') statement_id = self._get_param('StatementId') - events_backend.put_permission(action, principal, statement_id) + self.events_backend.put_permission(action, principal, statement_id) return '' def remove_permission(self): statement_id = self._get_param('StatementId') - events_backend.remove_permission(statement_id) + self.events_backend.remove_permission(statement_id) return '' def describe_event_bus(self): - return json.dumps(events_backend.describe_event_bus()) + return json.dumps(self.events_backend.describe_event_bus()) diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index a9d90ec32..e9e1d12c9 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -87,7 +87,7 @@ def test_describe_rule(): assert(response is not None) assert(response.get('Name') == rule_name) - assert(response.get('Arn') is not None) + assert(response.get('Arn') == 'arn:aws:events:us-west-2:111111111111:rule/{0}'.format(rule_name)) @mock_events From 2424d6368636d6efc50ef07da01ca970ff91108d Mon Sep 17 00:00:00 2001 From: Jessie Nadler Date: Thu, 3 Oct 2019 15:29:54 -0400 Subject: [PATCH 3/3] Use self.region_name to generate rule ARN --- moto/events/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/moto/events/models.py b/moto/events/models.py index 6e4a75d07..7871bae7b 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -9,15 +9,16 @@ from moto.core import BaseBackend, BaseModel class Rule(BaseModel): - def _generate_arn(self, name, region_name): + def _generate_arn(self, name): return 'arn:aws:events:{region_name}:111111111111:rule/{name}'.format( - region_name=region_name, + region_name=self.region_name, name=name ) def __init__(self, name, region_name, **kwargs): self.name = name - self.arn = kwargs.get('Arn') or self._generate_arn(name, region_name) + self.region_name = region_name + self.arn = kwargs.get('Arn') or self._generate_arn(name) self.event_pattern = kwargs.get('EventPattern') self.schedule_exp = kwargs.get('ScheduleExpression') self.state = kwargs.get('State') or 'ENABLED'