Merge pull request #2449 from nadlerjessie/events-and-ecs-multi-region

Events and ecs multi region
This commit is contained in:
Steve Pulec 2019-10-03 16:20:30 -05:00 committed by GitHub
commit 7de11b672b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 47 deletions

View File

@ -44,15 +44,17 @@ class BaseObject(BaseModel):
class Cluster(BaseObject): class Cluster(BaseObject):
def __init__(self, cluster_name): def __init__(self, cluster_name, region_name):
self.active_services_count = 0 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) cluster_name)
self.name = cluster_name self.name = cluster_name
self.pending_tasks_count = 0 self.pending_tasks_count = 0
self.registered_container_instances_count = 0 self.registered_container_instances_count = 0
self.running_tasks_count = 0 self.running_tasks_count = 0
self.status = 'ACTIVE' self.status = 'ACTIVE'
self.region_name = region_name
@property @property
def physical_resource_id(self): def physical_resource_id(self):
@ -108,11 +110,11 @@ class Cluster(BaseObject):
class TaskDefinition(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.family = family
self.revision = revision self.revision = revision
self.arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/{0}:{1}'.format( self.arn = 'arn:aws:ecs:{0}:012345678910:task-definition/{1}:{2}'.format(
family, revision) region_name, family, revision)
self.container_definitions = container_definitions self.container_definitions = container_definitions
self.tags = tags if tags is not None else [] self.tags = tags if tags is not None else []
if volumes is None: if volumes is None:
@ -172,7 +174,8 @@ class Task(BaseObject):
def __init__(self, cluster, task_definition, container_instance_arn, def __init__(self, cluster, task_definition, container_instance_arn,
resource_requirements, overrides={}, started_by=''): resource_requirements, overrides={}, started_by=''):
self.cluster_arn = cluster.arn 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())) str(uuid.uuid4()))
self.container_instance_arn = container_instance_arn self.container_instance_arn = container_instance_arn
self.last_status = 'RUNNING' 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, tags=None): def __init__(self, cluster, service_name, task_definition, desired_count, load_balancers=None, scheduling_strategy=None, tags=None):
self.cluster_arn = cluster.arn 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) service_name)
self.name = service_name self.name = service_name
self.status = 'ACTIVE' self.status = 'ACTIVE'
@ -274,7 +278,7 @@ class Service(BaseObject):
ecs_backend = ecs_backends[region_name] ecs_backend = ecs_backends[region_name]
service_name = original_resource.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: LoadBalancers
# TODO: Role # TODO: Role
ecs_backend.delete_service(cluster_name, service_name) ecs_backend.delete_service(cluster_name, service_name)
@ -321,7 +325,8 @@ class ContainerInstance(BaseObject):
'name': 'PORTS_UDP', 'name': 'PORTS_UDP',
'stringSetValue': [], 'stringSetValue': [],
'type': 'STRINGSET'}] '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())) str(uuid.uuid4()))
self.pending_tasks_count = 0 self.pending_tasks_count = 0
self.remaining_resources = [ self.remaining_resources = [
@ -379,9 +384,10 @@ class ContainerInstance(BaseObject):
class ClusterFailure(BaseObject): class ClusterFailure(BaseObject):
def __init__(self, reason, cluster_name): def __init__(self, reason, cluster_name, region_name):
self.reason = reason 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) cluster_name)
@property @property
@ -394,9 +400,10 @@ class ClusterFailure(BaseObject):
class ContainerInstanceFailure(BaseObject): class ContainerInstanceFailure(BaseObject):
def __init__(self, reason, container_instance_id): def __init__(self, reason, container_instance_id, region_name):
self.reason = reason 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) container_instance_id)
@property @property
@ -439,7 +446,7 @@ class EC2ContainerServiceBackend(BaseBackend):
"{0} is not a task_definition".format(task_definition_name)) "{0} is not a task_definition".format(task_definition_name))
def create_cluster(self, cluster_name): def create_cluster(self, cluster_name):
cluster = Cluster(cluster_name) cluster = Cluster(cluster_name, self.region_name)
self.clusters[cluster_name] = cluster self.clusters[cluster_name] = cluster
return cluster return cluster
@ -462,7 +469,7 @@ class EC2ContainerServiceBackend(BaseBackend):
list_clusters.append( list_clusters.append(
self.clusters[cluster_name].response_object) self.clusters[cluster_name].response_object)
else: else:
failures.append(ClusterFailure('MISSING', cluster_name)) failures.append(ClusterFailure('MISSING', cluster_name, self.region_name))
return list_clusters, failures return list_clusters, failures
def delete_cluster(self, cluster_str): def delete_cluster(self, cluster_str):
@ -480,7 +487,7 @@ class EC2ContainerServiceBackend(BaseBackend):
self.task_definitions[family] = {} self.task_definitions[family] = {}
revision = 1 revision = 1
task_definition = TaskDefinition( 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 self.task_definitions[family][revision] = task_definition
return task_definition return task_definition
@ -793,7 +800,7 @@ class EC2ContainerServiceBackend(BaseBackend):
container_instance_objects.append(container_instance) container_instance_objects.append(container_instance)
else: else:
failures.append(ContainerInstanceFailure( failures.append(ContainerInstanceFailure(
'MISSING', container_instance_id)) 'MISSING', container_instance_id, self.region_name))
return container_instance_objects, failures return container_instance_objects, failures
@ -815,7 +822,7 @@ class EC2ContainerServiceBackend(BaseBackend):
container_instance.status = status container_instance.status = status
container_instance_objects.append(container_instance) container_instance_objects.append(container_instance)
else: else:
failures.append(ContainerInstanceFailure('MISSING', container_instance_id)) failures.append(ContainerInstanceFailure('MISSING', container_instance_id, self.region_name))
return container_instance_objects, failures return container_instance_objects, failures

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals 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} events_backend = events_backends['us-east-1']
mock_events = events_backend.decorator mock_events = base_decorator(events_backends)

View File

@ -1,6 +1,7 @@
import os import os
import re import re
import json import json
import boto3
from moto.core.exceptions import JsonRESTError from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
@ -9,10 +10,14 @@ from moto.core import BaseBackend, BaseModel
class Rule(BaseModel): class Rule(BaseModel):
def _generate_arn(self, name): def _generate_arn(self, name):
return 'arn:aws:events:us-west-2:111111111111:rule/' + name return 'arn:aws:events:{region_name}:111111111111:rule/{name}'.format(
region_name=self.region_name,
name=name
)
def __init__(self, name, **kwargs): def __init__(self, name, region_name, **kwargs):
self.name = name self.name = name
self.region_name = region_name
self.arn = kwargs.get('Arn') or self._generate_arn(name) self.arn = kwargs.get('Arn') or self._generate_arn(name)
self.event_pattern = kwargs.get('EventPattern') self.event_pattern = kwargs.get('EventPattern')
self.schedule_exp = kwargs.get('ScheduleExpression') self.schedule_exp = kwargs.get('ScheduleExpression')
@ -55,15 +60,20 @@ class EventsBackend(BaseBackend):
ACCOUNT_ID = re.compile(r'^(\d{1,12}|\*)$') ACCOUNT_ID = re.compile(r'^(\d{1,12}|\*)$')
STATEMENT_ID = re.compile(r'^[a-zA-Z0-9-_]{1,64}$') STATEMENT_ID = re.compile(r'^[a-zA-Z0-9-_]{1,64}$')
def __init__(self): def __init__(self, region_name):
self.rules = {} self.rules = {}
# This array tracks the order in which the rules have been added, since # This array tracks the order in which the rules have been added, since
# 2.6 doesn't have OrderedDicts. # 2.6 doesn't have OrderedDicts.
self.rules_order = [] self.rules_order = []
self.next_tokens = {} self.next_tokens = {}
self.region_name = region_name
self.permissions = {} self.permissions = {}
def reset(self):
region_name = self.region_name
self.__dict__ = {}
self.__init__(region_name)
def _get_rule_by_index(self, i): def _get_rule_by_index(self, i):
return self.rules.get(self.rules_order[i]) return self.rules.get(self.rules_order[i])
@ -173,7 +183,7 @@ class EventsBackend(BaseBackend):
return return_obj return return_obj
def put_rule(self, name, **kwargs): def put_rule(self, name, **kwargs):
rule = Rule(name, **kwargs) rule = Rule(name, self.region_name, **kwargs)
self.rules[rule.name] = rule self.rules[rule.name] = rule
self.rules_order.append(rule.name) self.rules_order.append(rule.name)
return rule.arn return rule.arn
@ -229,7 +239,7 @@ class EventsBackend(BaseBackend):
raise JsonRESTError('ResourceNotFoundException', 'StatementId not found') raise JsonRESTError('ResourceNotFoundException', 'StatementId not found')
def describe_event_bus(self): 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 = [] statements = []
for statement_id, data in self.permissions.items(): for statement_id, data in self.permissions.items():
statements.append({ statements.append({
@ -248,4 +258,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}

View File

@ -2,11 +2,21 @@ import json
import re import re
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.events import events_backend from moto.events import events_backends
class EventsHandler(BaseResponse): 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): def _generate_rule_dict(self, rule):
return { return {
'Name': rule.name, 'Name': rule.name,
@ -40,7 +50,7 @@ class EventsHandler(BaseResponse):
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
events_backend.delete_rule(name) self.events_backend.delete_rule(name)
return '', self.response_headers return '', self.response_headers
@ -50,7 +60,7 @@ class EventsHandler(BaseResponse):
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
rule = events_backend.describe_rule(name) rule = self.events_backend.describe_rule(name)
if not rule: if not rule:
return self.error('ResourceNotFoundException', 'Rule test does not exist.') return self.error('ResourceNotFoundException', 'Rule test does not exist.')
@ -64,7 +74,7 @@ class EventsHandler(BaseResponse):
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') 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.error('ResourceNotFoundException', 'Rule ' + name + ' does not exist.')
return '', self.response_headers return '', self.response_headers
@ -75,7 +85,7 @@ class EventsHandler(BaseResponse):
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') 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.error('ResourceNotFoundException', 'Rule ' + name + ' does not exist.')
return '', self.response_headers return '', self.response_headers
@ -91,7 +101,7 @@ class EventsHandler(BaseResponse):
if not target_arn: if not target_arn:
return self.error('ValidationException', 'Parameter TargetArn is required.') 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) target_arn, next_token, limit)
return json.dumps(rule_names), self.response_headers return json.dumps(rule_names), self.response_headers
@ -101,7 +111,7 @@ class EventsHandler(BaseResponse):
next_token = self._get_param('NextToken') next_token = self._get_param('NextToken')
limit = self._get_param('Limit') 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': []} rules_obj = {'Rules': []}
for rule in rules['Rules']: for rule in rules['Rules']:
@ -121,7 +131,7 @@ class EventsHandler(BaseResponse):
return self.error('ValidationException', 'Parameter Rule is required.') return self.error('ValidationException', 'Parameter Rule is required.')
try: try:
targets = events_backend.list_targets_by_rule( targets = self.events_backend.list_targets_by_rule(
rule_name, next_token, limit) rule_name, next_token, limit)
except KeyError: except KeyError:
return self.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.') return self.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.')
@ -131,7 +141,7 @@ class EventsHandler(BaseResponse):
def put_events(self): def put_events(self):
events = self._get_param('Entries') events = self._get_param('Entries')
failed_entries = events_backend.put_events(events) failed_entries = self.events_backend.put_events(events)
if failed_entries: if failed_entries:
return json.dumps({ return json.dumps({
@ -165,7 +175,7 @@ class EventsHandler(BaseResponse):
re.match('^rate\(\d*\s(minute|minutes|hour|hours|day|days)\)', sched_exp)): re.match('^rate\(\d*\s(minute|minutes|hour|hours|day|days)\)', sched_exp)):
return self.error('ValidationException', 'Parameter ScheduleExpression is not valid.') return self.error('ValidationException', 'Parameter ScheduleExpression is not valid.')
rule_arn = events_backend.put_rule( rule_arn = self.events_backend.put_rule(
name, name,
ScheduleExpression=sched_exp, ScheduleExpression=sched_exp,
EventPattern=event_pattern, EventPattern=event_pattern,
@ -186,7 +196,7 @@ class EventsHandler(BaseResponse):
if not targets: if not targets:
return self.error('ValidationException', 'Parameter Targets is required.') 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.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.')
return '', self.response_headers return '', self.response_headers
@ -201,7 +211,7 @@ class EventsHandler(BaseResponse):
if not ids: if not ids:
return self.error('ValidationException', 'Parameter Ids is required.') 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.error('ResourceNotFoundException', 'Rule ' + rule_name + ' does not exist.')
return '', self.response_headers return '', self.response_headers
@ -214,16 +224,16 @@ class EventsHandler(BaseResponse):
principal = self._get_param('Principal') principal = self._get_param('Principal')
statement_id = self._get_param('StatementId') statement_id = self._get_param('StatementId')
events_backend.put_permission(action, principal, statement_id) self.events_backend.put_permission(action, principal, statement_id)
return '' return ''
def remove_permission(self): def remove_permission(self):
statement_id = self._get_param('StatementId') statement_id = self._get_param('StatementId')
events_backend.remove_permission(statement_id) self.events_backend.remove_permission(statement_id)
return '' return ''
def describe_event_bus(self): def describe_event_bus(self):
return json.dumps(events_backend.describe_event_bus()) return json.dumps(self.events_backend.describe_event_bus())

View File

@ -34,7 +34,7 @@ def test_create_cluster():
@mock_ecs @mock_ecs
def test_list_clusters(): 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( _ = client.create_cluster(
clusterName='test_cluster0' clusterName='test_cluster0'
) )
@ -43,9 +43,9 @@ def test_list_clusters():
) )
response = client.list_clusters() response = client.list_clusters()
response['clusterArns'].should.contain( 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( 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 @mock_ecs

View File

@ -87,7 +87,7 @@ def test_describe_rule():
assert(response is not None) assert(response is not None)
assert(response.get('Name') == rule_name) 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 @mock_events