This commit is contained in:
Steve Pulec 2017-02-23 21:37:43 -05:00
commit f37bad0e00
260 changed files with 6363 additions and 3766 deletions

View file

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from .models import swf_backends
from ..core.models import MockAWS, base_decorator, HttprettyMockAWS, deprecated_base_decorator
from ..core.models import base_decorator, deprecated_base_decorator
swf_backend = swf_backends['us-east-1']
mock_swf = base_decorator(swf_backends)

View file

@ -8,6 +8,7 @@ class SWFClientError(JsonRESTError):
class SWFUnknownResourceFault(SWFClientError):
def __init__(self, resource_type, resource_name=None):
if resource_name:
message = "Unknown {0}: {1}".format(resource_type, resource_name)
@ -20,6 +21,7 @@ class SWFUnknownResourceFault(SWFClientError):
class SWFDomainAlreadyExistsFault(SWFClientError):
def __init__(self, domain_name):
super(SWFDomainAlreadyExistsFault, self).__init__(
"com.amazonaws.swf.base.model#DomainAlreadyExistsFault",
@ -28,6 +30,7 @@ class SWFDomainAlreadyExistsFault(SWFClientError):
class SWFDomainDeprecatedFault(SWFClientError):
def __init__(self, domain_name):
super(SWFDomainDeprecatedFault, self).__init__(
"com.amazonaws.swf.base.model#DomainDeprecatedFault",
@ -36,9 +39,11 @@ class SWFDomainDeprecatedFault(SWFClientError):
class SWFSerializationException(SWFClientError):
def __init__(self, value):
message = "class java.lang.Foo can not be converted to an String "
message += " (not a real SWF exception ; happened on: {0})".format(value)
message += " (not a real SWF exception ; happened on: {0})".format(
value)
__type = "com.amazonaws.swf.base.model#SerializationException"
super(SWFSerializationException, self).__init__(
__type,
@ -47,22 +52,27 @@ class SWFSerializationException(SWFClientError):
class SWFTypeAlreadyExistsFault(SWFClientError):
def __init__(self, _type):
super(SWFTypeAlreadyExistsFault, self).__init__(
"com.amazonaws.swf.base.model#TypeAlreadyExistsFault",
"{0}=[name={1}, version={2}]".format(_type.__class__.__name__, _type.name, _type.version),
"{0}=[name={1}, version={2}]".format(
_type.__class__.__name__, _type.name, _type.version),
)
class SWFTypeDeprecatedFault(SWFClientError):
def __init__(self, _type):
super(SWFTypeDeprecatedFault, self).__init__(
"com.amazonaws.swf.base.model#TypeDeprecatedFault",
"{0}=[name={1}, version={2}]".format(_type.__class__.__name__, _type.name, _type.version),
"{0}=[name={1}, version={2}]".format(
_type.__class__.__name__, _type.name, _type.version),
)
class SWFWorkflowExecutionAlreadyStartedFault(SWFClientError):
def __init__(self):
super(SWFWorkflowExecutionAlreadyStartedFault, self).__init__(
"com.amazonaws.swf.base.model#WorkflowExecutionAlreadyStartedFault",
@ -71,6 +81,7 @@ class SWFWorkflowExecutionAlreadyStartedFault(SWFClientError):
class SWFDefaultUndefinedFault(SWFClientError):
def __init__(self, key):
# TODO: move that into moto.core.utils maybe?
words = key.split("_")
@ -84,6 +95,7 @@ class SWFDefaultUndefinedFault(SWFClientError):
class SWFValidationException(SWFClientError):
def __init__(self, message):
super(SWFValidationException, self).__init__(
"com.amazon.coral.validate#ValidationException",
@ -92,6 +104,7 @@ class SWFValidationException(SWFClientError):
class SWFDecisionValidationException(SWFClientError):
def __init__(self, problems):
# messages
messages = []
@ -109,7 +122,8 @@ class SWFDecisionValidationException(SWFClientError):
)
else:
raise ValueError(
"Unhandled decision constraint type: {0}".format(pb["type"])
"Unhandled decision constraint type: {0}".format(pb[
"type"])
)
# prefix
count = len(problems)
@ -124,5 +138,6 @@ class SWFDecisionValidationException(SWFClientError):
class SWFWorkflowExecutionClosedError(Exception):
def __str__(self):
return repr("Cannot change this object because the WorkflowExecution is closed")

View file

@ -12,15 +12,15 @@ from ..exceptions import (
SWFTypeDeprecatedFault,
SWFValidationException,
)
from .activity_task import ActivityTask
from .activity_type import ActivityType
from .decision_task import DecisionTask
from .domain import Domain
from .generic_type import GenericType
from .history_event import HistoryEvent
from .timeout import Timeout
from .workflow_type import WorkflowType
from .workflow_execution import WorkflowExecution
from .activity_task import ActivityTask # flake8: noqa
from .activity_type import ActivityType # flake8: noqa
from .decision_task import DecisionTask # flake8: noqa
from .domain import Domain # flake8: noqa
from .generic_type import GenericType # flake8: noqa
from .history_event import HistoryEvent # flake8: noqa
from .timeout import Timeout # flake8: noqa
from .workflow_type import WorkflowType # flake8: noqa
from .workflow_execution import WorkflowExecution # flake8: noqa
KNOWN_SWF_TYPES = {
@ -30,6 +30,7 @@ KNOWN_SWF_TYPES = {
class SWFBackend(BaseBackend):
def __init__(self, region_name):
self.region_name = region_name
self.domains = []
@ -246,7 +247,8 @@ class SWFBackend(BaseBackend):
if decision_task.state != "STARTED":
if decision_task.state == "COMPLETED":
raise SWFUnknownResourceFault(
"decision task, scheduledEventId = {0}".format(decision_task.scheduled_event_id)
"decision task, scheduledEventId = {0}".format(
decision_task.scheduled_event_id)
)
else:
raise ValueError(
@ -300,7 +302,8 @@ class SWFBackend(BaseBackend):
count = 0
for _task_list, tasks in domain.activity_task_lists.items():
if _task_list == task_list:
pending = [t for t in tasks if t.state in ["SCHEDULED", "STARTED"]]
pending = [t for t in tasks if t.state in [
"SCHEDULED", "STARTED"]]
count += len(pending)
return count
@ -330,7 +333,8 @@ class SWFBackend(BaseBackend):
if activity_task.state != "STARTED":
if activity_task.state == "COMPLETED":
raise SWFUnknownResourceFault(
"activity, scheduledEventId = {0}".format(activity_task.scheduled_event_id)
"activity, scheduledEventId = {0}".format(
activity_task.scheduled_event_id)
)
else:
raise ValueError(
@ -354,15 +358,18 @@ class SWFBackend(BaseBackend):
self._process_timeouts()
activity_task = self._find_activity_task_from_token(task_token)
wfe = activity_task.workflow_execution
wfe.fail_activity_task(activity_task.task_token, reason=reason, details=details)
wfe.fail_activity_task(activity_task.task_token,
reason=reason, details=details)
def terminate_workflow_execution(self, domain_name, workflow_id, child_policy=None,
details=None, reason=None, run_id=None):
# process timeouts on all objects
self._process_timeouts()
domain = self._get_domain(domain_name)
wfe = domain.get_workflow_execution(workflow_id, run_id=run_id, raise_if_closed=True)
wfe.terminate(child_policy=child_policy, details=details, reason=reason)
wfe = domain.get_workflow_execution(
workflow_id, run_id=run_id, raise_if_closed=True)
wfe.terminate(child_policy=child_policy,
details=details, reason=reason)
def record_activity_task_heartbeat(self, task_token, details=None):
# process timeouts on all objects

View file

@ -9,6 +9,7 @@ from .timeout import Timeout
class ActivityTask(object):
def __init__(self, activity_id, activity_type, scheduled_event_id,
workflow_execution, timeouts, input=None):
self.activity_id = activity_id

View file

@ -2,6 +2,7 @@ from .generic_type import GenericType
class ActivityType(GenericType):
@property
def _configuration_keys(self):
return [

View file

@ -9,6 +9,7 @@ from .timeout import Timeout
class DecisionTask(object):
def __init__(self, workflow_execution, scheduled_event_id):
self.workflow_execution = workflow_execution
self.workflow_type = workflow_execution.workflow_type
@ -60,7 +61,8 @@ class DecisionTask(object):
if not self.started or not self.workflow_execution.open:
return None
# TODO: handle the "NONE" case
start_to_close_at = self.started_timestamp + int(self.start_to_close_timeout)
start_to_close_at = self.started_timestamp + \
int(self.start_to_close_timeout)
_timeout = Timeout(self, start_to_close_at, "START_TO_CLOSE")
if _timeout.reached:
return _timeout

View file

@ -8,6 +8,7 @@ from ..exceptions import (
class Domain(object):
def __init__(self, name, retention, description=None):
self.name = name
self.retention = retention

View file

@ -4,6 +4,7 @@ from moto.core.utils import camelcase_to_underscores
class GenericType(object):
def __init__(self, name, version, **kwargs):
self.name = name
self.version = version

View file

@ -28,10 +28,12 @@ SUPPORTED_HISTORY_EVENT_TYPES = (
class HistoryEvent(object):
def __init__(self, event_id, event_type, event_timestamp=None, **kwargs):
if event_type not in SUPPORTED_HISTORY_EVENT_TYPES:
raise NotImplementedError(
"HistoryEvent does not implement attributes for type '{0}'".format(event_type)
"HistoryEvent does not implement attributes for type '{0}'".format(
event_type)
)
self.event_id = event_id
self.event_type = event_type

View file

@ -2,6 +2,7 @@ from moto.core.utils import unix_time
class Timeout(object):
def __init__(self, obj, timestamp, kind):
self.obj = obj
self.timestamp = timestamp

View file

@ -64,9 +64,12 @@ class WorkflowExecution(object):
# NB: the order follows boto/SWF order of exceptions appearance (if no
# param is set, # SWF will raise DefaultUndefinedFault errors in the
# same order as the few lines that follow)
self._set_from_kwargs_or_workflow_type(kwargs, "execution_start_to_close_timeout")
self._set_from_kwargs_or_workflow_type(kwargs, "task_list", "task_list")
self._set_from_kwargs_or_workflow_type(kwargs, "task_start_to_close_timeout")
self._set_from_kwargs_or_workflow_type(
kwargs, "execution_start_to_close_timeout")
self._set_from_kwargs_or_workflow_type(
kwargs, "task_list", "task_list")
self._set_from_kwargs_or_workflow_type(
kwargs, "task_start_to_close_timeout")
self._set_from_kwargs_or_workflow_type(kwargs, "child_policy")
self.input = kwargs.get("input")
# counters
@ -368,13 +371,16 @@ class WorkflowExecution(object):
# check decision types mandatory attributes
# NB: the real SWF service seems to check attributes even for attributes list
# that are not in line with the decisionType, so we do the same
attrs_to_check = [d for d in dcs.keys() if d.endswith("DecisionAttributes")]
attrs_to_check = [
d for d in dcs.keys() if d.endswith("DecisionAttributes")]
if dcs["decisionType"] in self.KNOWN_DECISION_TYPES:
decision_type = dcs["decisionType"]
decision_attr = "{0}DecisionAttributes".format(decapitalize(decision_type))
decision_attr = "{0}DecisionAttributes".format(
decapitalize(decision_type))
attrs_to_check.append(decision_attr)
for attr in attrs_to_check:
problems += self._check_decision_attributes(attr, dcs.get(attr, {}), decision_number)
problems += self._check_decision_attributes(
attr, dcs.get(attr, {}), decision_number)
# check decision type is correct
if dcs["decisionType"] not in self.KNOWN_DECISION_TYPES:
problems.append({
@ -396,12 +402,14 @@ class WorkflowExecution(object):
# handle each decision separately, in order
for decision in decisions:
decision_type = decision["decisionType"]
attributes_key = "{0}DecisionAttributes".format(decapitalize(decision_type))
attributes_key = "{0}DecisionAttributes".format(
decapitalize(decision_type))
attributes = decision.get(attributes_key, {})
if decision_type == "CompleteWorkflowExecution":
self.complete(event_id, attributes.get("result"))
elif decision_type == "FailWorkflowExecution":
self.fail(event_id, attributes.get("details"), attributes.get("reason"))
self.fail(event_id, attributes.get(
"details"), attributes.get("reason"))
elif decision_type == "ScheduleActivityTask":
self.schedule_activity_task(event_id, attributes)
else:
@ -415,7 +423,8 @@ class WorkflowExecution(object):
# TODO: implement Decision type: SignalExternalWorkflowExecution
# TODO: implement Decision type: StartChildWorkflowExecution
# TODO: implement Decision type: StartTimer
raise NotImplementedError("Cannot handle decision: {0}".format(decision_type))
raise NotImplementedError(
"Cannot handle decision: {0}".format(decision_type))
# finally decrement counter if and only if everything went well
self.open_counts["openDecisionTasks"] -= 1
@ -447,7 +456,8 @@ class WorkflowExecution(object):
def fail_schedule_activity_task(_type, _cause):
# TODO: implement other possible failure mode: OPEN_ACTIVITIES_LIMIT_EXCEEDED
# NB: some failure modes are not implemented and probably won't be implemented in
# the future, such as ACTIVITY_CREATION_RATE_EXCEEDED or OPERATION_NOT_PERMITTED
# the future, such as ACTIVITY_CREATION_RATE_EXCEEDED or
# OPERATION_NOT_PERMITTED
self._add_event(
"ScheduleActivityTaskFailed",
activity_id=attributes["activityId"],
@ -591,13 +601,15 @@ class WorkflowExecution(object):
def first_timeout(self):
if not self.open or not self.start_timestamp:
return None
start_to_close_at = self.start_timestamp + int(self.execution_start_to_close_timeout)
start_to_close_at = self.start_timestamp + \
int(self.execution_start_to_close_timeout)
_timeout = Timeout(self, start_to_close_at, "START_TO_CLOSE")
if _timeout.reached:
return _timeout
def timeout(self, timeout):
# TODO: process child policy on child workflows here or in the triggering function
# TODO: process child policy on child workflows here or in the
# triggering function
self.execution_status = "CLOSED"
self.close_status = "TIMED_OUT"
self.timeout_type = timeout.kind

View file

@ -2,6 +2,7 @@ from .generic_type import GenericType
class WorkflowType(GenericType):
@property
def _configuration_keys(self):
return [

View file

@ -64,7 +64,8 @@ class SWFResponse(BaseResponse):
reverse_order = self._params.get("reverseOrder", None)
self._check_string(domain_name)
self._check_string(status)
types = self.swf_backend.list_types(kind, domain_name, status, reverse_order=reverse_order)
types = self.swf_backend.list_types(
kind, domain_name, status, reverse_order=reverse_order)
return json.dumps({
"typeInfos": [_type.to_medium_dict() for _type in types]
})
@ -97,7 +98,8 @@ class SWFResponse(BaseResponse):
status = self._params["registrationStatus"]
self._check_string(status)
reverse_order = self._params.get("reverseOrder", None)
domains = self.swf_backend.list_domains(status, reverse_order=reverse_order)
domains = self.swf_backend.list_domains(
status, reverse_order=reverse_order)
return json.dumps({
"domainInfos": [domain.to_short_dict() for domain in domains]
})
@ -107,7 +109,8 @@ class SWFResponse(BaseResponse):
start_time_filter = self._params.get('startTimeFilter', None)
close_time_filter = self._params.get('closeTimeFilter', None)
execution_filter = self._params.get('executionFilter', None)
workflow_id = execution_filter['workflowId'] if execution_filter else None
workflow_id = execution_filter[
'workflowId'] if execution_filter else None
maximum_page_size = self._params.get('maximumPageSize', 1000)
reverse_order = self._params.get('reverseOrder', None)
tag_filter = self._params.get('tagFilter', None)
@ -162,7 +165,8 @@ class SWFResponse(BaseResponse):
domain = self._params['domain']
start_time_filter = self._params['startTimeFilter']
execution_filter = self._params.get('executionFilter', None)
workflow_id = execution_filter['workflowId'] if execution_filter else None
workflow_id = execution_filter[
'workflowId'] if execution_filter else None
maximum_page_size = self._params.get('maximumPageSize', 1000)
reverse_order = self._params.get('reverseOrder', None)
tag_filter = self._params.get('tagFilter', None)
@ -234,10 +238,14 @@ class SWFResponse(BaseResponse):
task_list = default_task_list.get("name")
else:
task_list = None
default_task_heartbeat_timeout = self._params.get("defaultTaskHeartbeatTimeout")
default_task_schedule_to_close_timeout = self._params.get("defaultTaskScheduleToCloseTimeout")
default_task_schedule_to_start_timeout = self._params.get("defaultTaskScheduleToStartTimeout")
default_task_start_to_close_timeout = self._params.get("defaultTaskStartToCloseTimeout")
default_task_heartbeat_timeout = self._params.get(
"defaultTaskHeartbeatTimeout")
default_task_schedule_to_close_timeout = self._params.get(
"defaultTaskScheduleToCloseTimeout")
default_task_schedule_to_start_timeout = self._params.get(
"defaultTaskScheduleToStartTimeout")
default_task_start_to_close_timeout = self._params.get(
"defaultTaskStartToCloseTimeout")
description = self._params.get("description")
self._check_string(domain)
@ -280,8 +288,10 @@ class SWFResponse(BaseResponse):
else:
task_list = None
default_child_policy = self._params.get("defaultChildPolicy")
default_task_start_to_close_timeout = self._params.get("defaultTaskStartToCloseTimeout")
default_execution_start_to_close_timeout = self._params.get("defaultExecutionStartToCloseTimeout")
default_task_start_to_close_timeout = self._params.get(
"defaultTaskStartToCloseTimeout")
default_execution_start_to_close_timeout = self._params.get(
"defaultExecutionStartToCloseTimeout")
description = self._params.get("description")
self._check_string(domain)
@ -322,10 +332,12 @@ class SWFResponse(BaseResponse):
else:
task_list = None
child_policy = self._params.get("childPolicy")
execution_start_to_close_timeout = self._params.get("executionStartToCloseTimeout")
execution_start_to_close_timeout = self._params.get(
"executionStartToCloseTimeout")
input_ = self._params.get("input")
tag_list = self._params.get("tagList")
task_start_to_close_timeout = self._params.get("taskStartToCloseTimeout")
task_start_to_close_timeout = self._params.get(
"taskStartToCloseTimeout")
self._check_string(domain)
self._check_string(workflow_id)
@ -360,7 +372,8 @@ class SWFResponse(BaseResponse):
self._check_string(run_id)
self._check_string(workflow_id)
wfe = self.swf_backend.describe_workflow_execution(domain_name, run_id, workflow_id)
wfe = self.swf_backend.describe_workflow_execution(
domain_name, run_id, workflow_id)
return json.dumps(wfe.to_full_dict())
def get_workflow_execution_history(self):
@ -369,7 +382,8 @@ class SWFResponse(BaseResponse):
run_id = _workflow_execution["runId"]
workflow_id = _workflow_execution["workflowId"]
reverse_order = self._params.get("reverseOrder", None)
wfe = self.swf_backend.describe_workflow_execution(domain_name, run_id, workflow_id)
wfe = self.swf_backend.describe_workflow_execution(
domain_name, run_id, workflow_id)
events = wfe.events(reverse_order=reverse_order)
return json.dumps({
"events": [evt.to_dict() for evt in events]
@ -399,7 +413,8 @@ class SWFResponse(BaseResponse):
task_list = self._params["taskList"]["name"]
self._check_string(domain_name)
self._check_string(task_list)
count = self.swf_backend.count_pending_decision_tasks(domain_name, task_list)
count = self.swf_backend.count_pending_decision_tasks(
domain_name, task_list)
return json.dumps({"count": count, "truncated": False})
def respond_decision_task_completed(self):
@ -435,7 +450,8 @@ class SWFResponse(BaseResponse):
task_list = self._params["taskList"]["name"]
self._check_string(domain_name)
self._check_string(task_list)
count = self.swf_backend.count_pending_activity_tasks(domain_name, task_list)
count = self.swf_backend.count_pending_activity_tasks(
domain_name, task_list)
return json.dumps({"count": count, "truncated": False})
def respond_activity_task_completed(self):
@ -453,7 +469,8 @@ class SWFResponse(BaseResponse):
reason = self._params.get("reason")
details = self._params.get("details")
self._check_string(task_token)
# TODO: implement length limits on reason and details (common pb with client libs)
# TODO: implement length limits on reason and details (common pb with
# client libs)
self._check_none_or_string(reason)
self._check_none_or_string(details)
self.swf_backend.respond_activity_task_failed(