Make SWF events formatting more generic

(suggested in @spulec review)
This commit is contained in:
Jean-Baptiste Barth 2015-11-23 14:51:58 +01:00
commit 566a90800e
3 changed files with 87 additions and 174 deletions

View file

@ -2,168 +2,65 @@ from __future__ import unicode_literals
from datetime import datetime
from time import mktime
from moto.core.utils import underscores_to_camelcase
from ..utils import decapitalize, now_timestamp
# We keep track of which history event types we support
# so that we'll be able to catch specific formatting
# for new events if needed.
SUPPORTED_HISTORY_EVENT_TYPES = (
"WorkflowExecutionStarted",
"DecisionTaskScheduled",
"DecisionTaskStarted",
"DecisionTaskCompleted",
"WorkflowExecutionCompleted",
"WorkflowExecutionFailed",
"ActivityTaskScheduled",
"ScheduleActivityTaskFailed",
"ActivityTaskStarted",
"ActivityTaskCompleted",
"ActivityTaskFailed",
"WorkflowExecutionTerminated",
"ActivityTaskTimedOut",
"DecisionTaskTimedOut",
"WorkflowExecutionTimedOut",
)
class HistoryEvent(object):
def __init__(self, event_id, event_type, **kwargs):
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)
)
self.event_id = event_id
self.event_type = event_type
self.event_timestamp = now_timestamp()
if event_timestamp:
self.event_timestamp = event_timestamp
else:
self.event_timestamp = now_timestamp()
# pre-populate a dict: {"camelCaseKey": value}
self.event_attributes = {}
for key, value in kwargs.items():
self.__setattr__(key, value)
# break soon if attributes are not valid
self.event_attributes()
if value:
camel_key = underscores_to_camelcase(key)
if key == "task_list":
value = { "name": value }
elif key == "workflow_type":
value = { "name": value.name, "version": value.version }
elif key == "activity_type":
value = value.to_short_dict()
self.event_attributes[camel_key] = value
def to_dict(self):
return {
"eventId": self.event_id,
"eventType": self.event_type,
"eventTimestamp": self.event_timestamp,
self._attributes_key(): self.event_attributes()
self._attributes_key(): self.event_attributes
}
def _attributes_key(self):
key = "{0}EventAttributes".format(self.event_type)
return decapitalize(key)
def event_attributes(self):
if self.event_type == "WorkflowExecutionStarted":
wfe = self.workflow_execution
hsh = {
"childPolicy": wfe.child_policy,
"executionStartToCloseTimeout": wfe.execution_start_to_close_timeout,
"parentInitiatedEventId": 0,
"taskList": {
"name": wfe.task_list
},
"taskStartToCloseTimeout": wfe.task_start_to_close_timeout,
"workflowType": {
"name": wfe.workflow_type.name,
"version": wfe.workflow_type.version
}
}
return hsh
elif self.event_type == "DecisionTaskScheduled":
wfe = self.workflow_execution
return {
"startToCloseTimeout": wfe.task_start_to_close_timeout,
"taskList": {"name": wfe.task_list}
}
elif self.event_type == "DecisionTaskStarted":
hsh = {
"scheduledEventId": self.scheduled_event_id
}
if hasattr(self, "identity") and self.identity:
hsh["identity"] = self.identity
return hsh
elif self.event_type == "DecisionTaskCompleted":
hsh = {
"scheduledEventId": self.scheduled_event_id,
"startedEventId": self.started_event_id,
}
if hasattr(self, "execution_context") and self.execution_context:
hsh["executionContext"] = self.execution_context
return hsh
elif self.event_type == "WorkflowExecutionCompleted":
hsh = {
"decisionTaskCompletedEventId": self.decision_task_completed_event_id,
}
if hasattr(self, "result") and self.result:
hsh["result"] = self.result
return hsh
elif self.event_type == "WorkflowExecutionFailed":
hsh = {
"decisionTaskCompletedEventId": self.decision_task_completed_event_id,
}
if hasattr(self, "details") and self.details:
hsh["details"] = self.details
if hasattr(self, "reason") and self.reason:
hsh["reason"] = self.reason
return hsh
elif self.event_type == "ActivityTaskScheduled":
hsh = {
"activityId": self.attributes["activityId"],
"activityType": self.activity_type.to_short_dict(),
"decisionTaskCompletedEventId": self.decision_task_completed_event_id,
"taskList": {
"name": self.task_list,
},
}
for attr in ["control", "heartbeatTimeout", "input", "scheduleToCloseTimeout",
"scheduleToStartTimeout", "startToCloseTimeout", "taskPriority"]:
if self.attributes.get(attr):
hsh[attr] = self.attributes[attr]
return hsh
elif self.event_type == "ScheduleActivityTaskFailed":
# 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
return {
"activityId": self.activity_id,
"activityType": self.activity_type.to_short_dict(),
"cause": self.cause,
"decisionTaskCompletedEventId": self.decision_task_completed_event_id,
}
elif self.event_type == "ActivityTaskStarted":
# TODO: merge it with DecisionTaskStarted
hsh = {
"scheduledEventId": self.scheduled_event_id
}
if hasattr(self, "identity") and self.identity:
hsh["identity"] = self.identity
return hsh
elif self.event_type == "ActivityTaskCompleted":
hsh = {
"scheduledEventId": self.scheduled_event_id,
"startedEventId": self.started_event_id,
}
if hasattr(self, "result") and self.result is not None:
hsh["result"] = self.result
return hsh
elif self.event_type == "ActivityTaskFailed":
# TODO: maybe merge it with ActivityTaskCompleted (different optional params tho)
hsh = {
"scheduledEventId": self.scheduled_event_id,
"startedEventId": self.started_event_id,
}
if hasattr(self, "reason") and self.reason is not None:
hsh["reason"] = self.reason
if hasattr(self, "details") and self.details is not None:
hsh["details"] = self.details
return hsh
elif self.event_type == "WorkflowExecutionTerminated":
hsh = {
"childPolicy": self.child_policy,
}
if self.cause:
hsh["cause"] = self.cause
if self.details:
hsh["details"] = self.details
if self.reason:
hsh["reason"] = self.reason
return hsh
elif self.event_type == "ActivityTaskTimedOut":
hsh = {
"scheduledEventId": self.scheduled_event_id,
"startedEventId": self.started_event_id,
"timeoutType": self.timeout_type,
}
if self.details:
hsh["details"] = self.details
return hsh
elif self.event_type == "DecisionTaskTimedOut":
return {
"scheduledEventId": self.scheduled_event_id,
"startedEventId": self.started_event_id,
"timeoutType": self.timeout_type,
}
elif self.event_type == "WorkflowExecutionTimedOut":
return {
"childPolicy": self.child_policy,
"timeoutType": self.timeout_type,
}
else:
raise NotImplementedError(
"HistoryEvent does not implement attributes for type '{0}'".format(self.event_type)
)

View file

@ -228,14 +228,21 @@ class WorkflowExecution(object):
self.start_timestamp = now_timestamp()
self._add_event(
"WorkflowExecutionStarted",
workflow_execution=self,
child_policy=self.child_policy,
execution_start_to_close_timeout=self.execution_start_to_close_timeout,
# TODO: fix this hardcoded value
parent_initiated_event_id=0,
task_list=self.task_list,
task_start_to_close_timeout=self.task_start_to_close_timeout,
workflow_type=self.workflow_type,
)
self.schedule_decision_task()
def _schedule_decision_task(self):
evt = self._add_event(
"DecisionTaskScheduled",
workflow_execution=self,
start_to_close_timeout=self.task_start_to_close_timeout,
task_list=self.task_list,
)
self.domain.add_to_decision_task_list(
self.task_list,
@ -274,7 +281,6 @@ class WorkflowExecution(object):
dt = self._find_decision_task(task_token)
evt = self._add_event(
"DecisionTaskStarted",
workflow_execution=self,
scheduled_event_id=dt.scheduled_event_id,
identity=identity
)
@ -419,6 +425,9 @@ class WorkflowExecution(object):
def schedule_activity_task(self, event_id, attributes):
# Helper function to avoid repeating ourselves in the next sections
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
self._add_event(
"ScheduleActivityTaskFailed",
activity_id=attributes["activityId"],
@ -473,10 +482,17 @@ class WorkflowExecution(object):
# Only add event and increment counters now that nothing went wrong
evt = self._add_event(
"ActivityTaskScheduled",
decision_task_completed_event_id=event_id,
activity_id=attributes["activityId"],
activity_type=activity_type,
attributes=attributes,
control=attributes.get("control"),
decision_task_completed_event_id=event_id,
heartbeat_timeout=attributes.get("heartbeatTimeout"),
input=attributes.get("input"),
schedule_to_close_timeout=attributes.get("scheduleToCloseTimeout"),
schedule_to_start_timeout=attributes.get("scheduleToStartTimeout"),
start_to_close_timeout=attributes.get("startToCloseTimeout"),
task_list=task_list,
task_priority=attributes.get("taskPriority"),
)
task = ActivityTask(
activity_id=attributes["activityId"],