Add SWF endpoint RecordActivityTaskHeartbeat
This commit is contained in:
parent
804d2e91b5
commit
f576f3765c
@ -364,6 +364,12 @@ class SWFBackend(BaseBackend):
|
|||||||
wfe = domain.get_workflow_execution(workflow_id, run_id=run_id, raise_if_closed=True)
|
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.terminate(child_policy=child_policy, details=details, reason=reason)
|
||||||
|
|
||||||
|
def record_activity_task_heartbeat(self, task_token, details=None):
|
||||||
|
self._check_string(task_token)
|
||||||
|
self._check_none_or_string(details)
|
||||||
|
activity_task = self._find_activity_task_from_token(task_token)
|
||||||
|
activity_task.reset_heartbeat_clock()
|
||||||
|
|
||||||
|
|
||||||
swf_backends = {}
|
swf_backends = {}
|
||||||
for region in boto.swf.regions():
|
for region in boto.swf.regions():
|
||||||
|
@ -2,6 +2,8 @@ from __future__ import unicode_literals
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from ..utils import now_timestamp
|
||||||
|
|
||||||
|
|
||||||
class ActivityTask(object):
|
class ActivityTask(object):
|
||||||
def __init__(self, activity_id, activity_type, scheduled_event_id,
|
def __init__(self, activity_id, activity_type, scheduled_event_id,
|
||||||
@ -9,6 +11,7 @@ class ActivityTask(object):
|
|||||||
self.activity_id = activity_id
|
self.activity_id = activity_id
|
||||||
self.activity_type = activity_type
|
self.activity_type = activity_type
|
||||||
self.input = input
|
self.input = input
|
||||||
|
self.last_heartbeat_timestamp = now_timestamp()
|
||||||
self.scheduled_event_id = scheduled_event_id
|
self.scheduled_event_id = scheduled_event_id
|
||||||
self.started_event_id = None
|
self.started_event_id = None
|
||||||
self.state = "SCHEDULED"
|
self.state = "SCHEDULED"
|
||||||
@ -39,3 +42,6 @@ class ActivityTask(object):
|
|||||||
|
|
||||||
def fail(self):
|
def fail(self):
|
||||||
self.state = "FAILED"
|
self.state = "FAILED"
|
||||||
|
|
||||||
|
def reset_heartbeat_clock(self):
|
||||||
|
self.last_heartbeat_timestamp = now_timestamp()
|
||||||
|
@ -2,14 +2,14 @@ from __future__ import unicode_literals
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import mktime
|
from time import mktime
|
||||||
|
|
||||||
from ..utils import decapitalize
|
from ..utils import decapitalize, now_timestamp
|
||||||
|
|
||||||
|
|
||||||
class HistoryEvent(object):
|
class HistoryEvent(object):
|
||||||
def __init__(self, event_id, event_type, **kwargs):
|
def __init__(self, event_id, event_type, **kwargs):
|
||||||
self.event_id = event_id
|
self.event_id = event_id
|
||||||
self.event_type = event_type
|
self.event_type = event_type
|
||||||
self.event_timestamp = float(mktime(datetime.now().timetuple()))
|
self.event_timestamp = now_timestamp()
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in kwargs.iteritems():
|
||||||
self.__setattr__(key, value)
|
self.__setattr__(key, value)
|
||||||
# break soon if attributes are not valid
|
# break soon if attributes are not valid
|
||||||
|
@ -13,7 +13,7 @@ from ..exceptions import (
|
|||||||
SWFValidationException,
|
SWFValidationException,
|
||||||
SWFDecisionValidationException,
|
SWFDecisionValidationException,
|
||||||
)
|
)
|
||||||
from ..utils import decapitalize
|
from ..utils import decapitalize, now_timestamp
|
||||||
from .activity_task import ActivityTask
|
from .activity_task import ActivityTask
|
||||||
from .activity_type import ActivityType
|
from .activity_type import ActivityType
|
||||||
from .decision_task import DecisionTask
|
from .decision_task import DecisionTask
|
||||||
@ -161,12 +161,8 @@ class WorkflowExecution(object):
|
|||||||
self._events.append(evt)
|
self._events.append(evt)
|
||||||
return evt
|
return evt
|
||||||
|
|
||||||
# TODO: move it in utils
|
|
||||||
def _now_timestamp(self):
|
|
||||||
return float(mktime(datetime.now().timetuple()))
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.start_timestamp = self._now_timestamp()
|
self.start_timestamp = now_timestamp()
|
||||||
self._add_event(
|
self._add_event(
|
||||||
"WorkflowExecutionStarted",
|
"WorkflowExecutionStarted",
|
||||||
workflow_execution=self,
|
workflow_execution=self,
|
||||||
@ -333,7 +329,7 @@ class WorkflowExecution(object):
|
|||||||
def complete(self, event_id, result=None):
|
def complete(self, event_id, result=None):
|
||||||
self.execution_status = "CLOSED"
|
self.execution_status = "CLOSED"
|
||||||
self.close_status = "COMPLETED"
|
self.close_status = "COMPLETED"
|
||||||
self.close_timestamp = self._now_timestamp()
|
self.close_timestamp = now_timestamp()
|
||||||
evt = self._add_event(
|
evt = self._add_event(
|
||||||
"WorkflowExecutionCompleted",
|
"WorkflowExecutionCompleted",
|
||||||
decision_task_completed_event_id=event_id,
|
decision_task_completed_event_id=event_id,
|
||||||
@ -344,7 +340,7 @@ class WorkflowExecution(object):
|
|||||||
# TODO: implement lenght constraints on details/reason
|
# TODO: implement lenght constraints on details/reason
|
||||||
self.execution_status = "CLOSED"
|
self.execution_status = "CLOSED"
|
||||||
self.close_status = "FAILED"
|
self.close_status = "FAILED"
|
||||||
self.close_timestamp = self._now_timestamp()
|
self.close_timestamp = now_timestamp()
|
||||||
evt = self._add_event(
|
evt = self._add_event(
|
||||||
"WorkflowExecutionFailed",
|
"WorkflowExecutionFailed",
|
||||||
decision_task_completed_event_id=event_id,
|
decision_task_completed_event_id=event_id,
|
||||||
@ -423,7 +419,7 @@ class WorkflowExecution(object):
|
|||||||
)
|
)
|
||||||
self.domain.add_to_activity_task_list(task_list, task)
|
self.domain.add_to_activity_task_list(task_list, task)
|
||||||
self.open_counts["openActivityTasks"] += 1
|
self.open_counts["openActivityTasks"] += 1
|
||||||
self.latest_activity_task_timestamp = self._now_timestamp()
|
self.latest_activity_task_timestamp = now_timestamp()
|
||||||
|
|
||||||
def _find_activity_task(self, task_token):
|
def _find_activity_task(self, task_token):
|
||||||
for task in self.activity_tasks:
|
for task in self.activity_tasks:
|
||||||
|
@ -300,3 +300,12 @@ class SWFResponse(BaseResponse):
|
|||||||
details=details, reason=reason, run_id=run_id
|
details=details, reason=reason, run_id=run_id
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def record_activity_task_heartbeat(self):
|
||||||
|
task_token = self._params["taskToken"]
|
||||||
|
details = self._params.get("details")
|
||||||
|
self.swf_backend.record_activity_task_heartbeat(
|
||||||
|
task_token, details=details
|
||||||
|
)
|
||||||
|
# TODO: make it dynamic when we implement activity tasks cancellation
|
||||||
|
return json.dumps({"cancelRequested": False})
|
||||||
|
@ -1,2 +1,9 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from time import mktime
|
||||||
|
|
||||||
|
|
||||||
def decapitalize(key):
|
def decapitalize(key):
|
||||||
return key[0].lower() + key[1:]
|
return key[0].lower() + key[1:]
|
||||||
|
|
||||||
|
def now_timestamp():
|
||||||
|
return float(mktime(datetime.now().timetuple()))
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from freezegun import freeze_time
|
||||||
from sure import expect
|
from sure import expect
|
||||||
|
|
||||||
from moto.swf.models import (
|
from moto.swf.models import (
|
||||||
@ -58,3 +59,22 @@ def test_activity_task_full_dict_representation():
|
|||||||
at.start(1234)
|
at.start(1234)
|
||||||
fd = at.to_full_dict()
|
fd = at.to_full_dict()
|
||||||
fd["startedEventId"].should.equal(1234)
|
fd["startedEventId"].should.equal(1234)
|
||||||
|
|
||||||
|
def test_activity_task_reset_heartbeat_clock():
|
||||||
|
wfe = make_workflow_execution()
|
||||||
|
|
||||||
|
with freeze_time("2015-01-01 12:00:00"):
|
||||||
|
task = ActivityTask(
|
||||||
|
activity_id="my-activity-123",
|
||||||
|
activity_type="foo",
|
||||||
|
input="optional",
|
||||||
|
scheduled_event_id=117,
|
||||||
|
workflow_execution=wfe,
|
||||||
|
)
|
||||||
|
|
||||||
|
task.last_heartbeat_timestamp.should.equal(1420110000.0)
|
||||||
|
|
||||||
|
with freeze_time("2015-01-01 13:00:00"):
|
||||||
|
task.reset_heartbeat_clock()
|
||||||
|
|
||||||
|
task.last_heartbeat_timestamp.should.equal(1420113600.0)
|
||||||
|
@ -171,3 +171,31 @@ def test_respond_activity_task_completed_with_wrong_token():
|
|||||||
conn.respond_activity_task_failed.when.called_with(
|
conn.respond_activity_task_failed.when.called_with(
|
||||||
"not-a-correct-token"
|
"not-a-correct-token"
|
||||||
).should.throw(SWFValidationException, "Invalid token")
|
).should.throw(SWFValidationException, "Invalid token")
|
||||||
|
|
||||||
|
|
||||||
|
# RecordActivityTaskHeartbeat endpoint
|
||||||
|
@mock_swf
|
||||||
|
def test_record_activity_task_heartbeat():
|
||||||
|
conn = setup_workflow()
|
||||||
|
decision_token = conn.poll_for_decision_task("test-domain", "queue")["taskToken"]
|
||||||
|
conn.respond_decision_task_completed(decision_token, decisions=[
|
||||||
|
SCHEDULE_ACTIVITY_TASK_DECISION
|
||||||
|
])
|
||||||
|
activity_token = conn.poll_for_activity_task("test-domain", "activity-task-list")["taskToken"]
|
||||||
|
|
||||||
|
resp = conn.record_activity_task_heartbeat(activity_token, details="some progress details")
|
||||||
|
# TODO: check that "details" are reflected in ActivityTaskTimedOut event when a timeout occurs
|
||||||
|
resp.should.equal({"cancelRequested": False})
|
||||||
|
|
||||||
|
@mock_swf
|
||||||
|
def test_record_activity_task_heartbeat_with_wrong_token():
|
||||||
|
conn = setup_workflow()
|
||||||
|
decision_token = conn.poll_for_decision_task("test-domain", "queue")["taskToken"]
|
||||||
|
conn.respond_decision_task_completed(decision_token, decisions=[
|
||||||
|
SCHEDULE_ACTIVITY_TASK_DECISION
|
||||||
|
])
|
||||||
|
conn.poll_for_activity_task("test-domain", "activity-task-list")["taskToken"]
|
||||||
|
|
||||||
|
conn.record_activity_task_heartbeat.when.called_with(
|
||||||
|
"bad-token", details="some progress details"
|
||||||
|
).should.throw(SWFValidationException)
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
|
from freezegun import freeze_time
|
||||||
from sure import expect
|
from sure import expect
|
||||||
from moto.swf.utils import decapitalize
|
|
||||||
|
from moto.swf.utils import (
|
||||||
|
decapitalize,
|
||||||
|
now_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_decapitalize():
|
def test_decapitalize():
|
||||||
cases = {
|
cases = {
|
||||||
@ -9,3 +15,7 @@ def test_decapitalize():
|
|||||||
}
|
}
|
||||||
for before, after in cases.iteritems():
|
for before, after in cases.iteritems():
|
||||||
decapitalize(before).should.equal(after)
|
decapitalize(before).should.equal(after)
|
||||||
|
|
||||||
|
@freeze_time("2015-01-01 12:00:00")
|
||||||
|
def test_now_timestamp():
|
||||||
|
now_timestamp().should.equal(1420110000.0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user