ntswf 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +2 -1
- data/lib/ntswf/client.rb +10 -4
- data/lib/ntswf/decision_worker.rb +23 -13
- data/spec/decision_worker_spec.rb +214 -0
- metadata +4 -2
data/README.md
CHANGED
@@ -9,6 +9,7 @@ AWS Simple Workflow.
|
|
9
9
|
[](http://badge.fury.io/rb/ntswf)
|
10
10
|
[](https://codeclimate.com/github/infopark/ntswf)
|
11
11
|
[](https://gemnasium.com/infopark/ntswf)
|
12
|
+
[](https://travis-ci.org/infopark/ntswf)
|
12
13
|
|
13
14
|
Usage
|
14
15
|
-----
|
@@ -69,4 +70,4 @@ See {Ntswf::Utils}
|
|
69
70
|
|
70
71
|
License
|
71
72
|
-------
|
72
|
-
[LPGLv3](http://www.gnu.org/licenses/lgpl-3.0.html)
|
73
|
+
[LPGLv3](http://www.gnu.org/licenses/lgpl-3.0.html)
|
data/lib/ntswf/client.rb
CHANGED
@@ -10,12 +10,18 @@ module Ntswf
|
|
10
10
|
# The options configure the control flow of the task.
|
11
11
|
# Excluding *:execution_id* they will be stored in the *input* argument of the task as JSON.
|
12
12
|
# @param options [Hash] The task's options. Keys with special meaning:
|
13
|
-
# @option options [String] :execution_id
|
14
|
-
#
|
13
|
+
# @option options [String] :execution_id
|
14
|
+
# Mandatory workflow ID suffix, allowed IDs are documented at docs.amazonwebservices.com
|
15
|
+
# (WorkflowId Property)
|
16
|
+
# @option options [Numeric] :interval
|
17
|
+
# Optional, in seconds. Enforces periodic start of
|
18
|
+
# new executions, even in case of failure
|
15
19
|
# @option options [String] :name Identifies the kind of task for the executing unit
|
16
20
|
# @option options [Hash] :params Custom task parameters passed on to the executing unit
|
17
|
-
# @option options [String] :unit
|
18
|
-
#
|
21
|
+
# @option options [String] :unit
|
22
|
+
# The executing unit's key, a corresponding activity task list must be configured
|
23
|
+
# @option options [Numeric] :version
|
24
|
+
# Optional minimum version of the client. The task may be rescheduled by older clients.
|
19
25
|
# @return [AWS::SimpleWorkflow::WorkflowExecution]
|
20
26
|
# @raise [AWS::SimpleWorkflow::Errors::WorkflowExecutionAlreadyStartedFault]
|
21
27
|
def start_execution(options)
|
@@ -11,7 +11,9 @@ module Ntswf
|
|
11
11
|
# reason:: reschedule if {RETRY}
|
12
12
|
# result:: Interpreted as {Hash}, see below for keys
|
13
13
|
# Result keys
|
14
|
-
# :seconds_until_retry::
|
14
|
+
# :seconds_until_retry::
|
15
|
+
# Planned re-schedule after task completion. Please note that
|
16
|
+
# given an *:interval* option the behaviour of this key is undefined
|
15
17
|
def process_decision_task
|
16
18
|
announce("polling for decision task #{decision_task_list}")
|
17
19
|
domain.decision_tasks.poll_for_single_task(decision_task_list) do |task|
|
@@ -33,17 +35,7 @@ module Ntswf
|
|
33
35
|
when 'WorkflowExecutionStarted'
|
34
36
|
schedule(task, event)
|
35
37
|
when 'TimerFired'
|
36
|
-
|
37
|
-
keys = [
|
38
|
-
:child_policy,
|
39
|
-
:execution_start_to_close_timeout,
|
40
|
-
:input,
|
41
|
-
:tag_list,
|
42
|
-
:task_list,
|
43
|
-
:task_start_to_close_timeout,
|
44
|
-
]
|
45
|
-
attributes = start_event.attributes.to_h.keep_if {|k| keys.include? k}
|
46
|
-
task.continue_as_new_workflow_execution(attributes)
|
38
|
+
retry_or_continue_as_new(task, task.events.first)
|
47
39
|
when 'ActivityTaskCompleted'
|
48
40
|
result = parse_result(event.attributes.result)
|
49
41
|
start_timer(task, result["seconds_until_retry"]) or task.complete_workflow_execution(
|
@@ -53,7 +45,7 @@ module Ntswf
|
|
53
45
|
schedule(task, task.events.first)
|
54
46
|
else
|
55
47
|
start_timer(task) or task.fail_workflow_execution(
|
56
|
-
event.attributes.to_h.
|
48
|
+
event.attributes.to_h.keep_if {|k| [:details, :reason].include? k})
|
57
49
|
end
|
58
50
|
when 'ActivityTaskTimedOut'
|
59
51
|
notify("Timeout in Simple Workflow. Possible cause: all workers busy",
|
@@ -72,6 +64,24 @@ module Ntswf
|
|
72
64
|
interval
|
73
65
|
end
|
74
66
|
|
67
|
+
def retry_or_continue_as_new(task, original_event)
|
68
|
+
options = parse_input(original_event.attributes.input)
|
69
|
+
if options['interval']
|
70
|
+
keys = [
|
71
|
+
:child_policy,
|
72
|
+
:execution_start_to_close_timeout,
|
73
|
+
:input,
|
74
|
+
:tag_list,
|
75
|
+
:task_list,
|
76
|
+
:task_start_to_close_timeout,
|
77
|
+
]
|
78
|
+
attributes = original_event.attributes.to_h.keep_if {|k| keys.include? k}
|
79
|
+
task.continue_as_new_workflow_execution(attributes)
|
80
|
+
else
|
81
|
+
schedule(task, original_event)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
75
85
|
def schedule(task, data_providing_event)
|
76
86
|
input = data_providing_event.attributes.input
|
77
87
|
options = parse_input(input)
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require "ntswf"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
describe Ntswf::DecisionWorker do
|
5
|
+
class Worker
|
6
|
+
include Ntswf::DecisionWorker
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:atl_config) { { "test" => "atl" } }
|
10
|
+
let(:dtl_config) { "dtl" }
|
11
|
+
let(:unit) { "testt" }
|
12
|
+
let(:config) { { unit: unit, decision_task_list: dtl_config, activity_task_lists: atl_config } }
|
13
|
+
let(:worker) { Worker.new config }
|
14
|
+
|
15
|
+
let(:options) { {} }
|
16
|
+
let(:input) { options.merge('params' => {'test' => 'value'}).to_json }
|
17
|
+
let(:reason) { nil }
|
18
|
+
let(:result) { nil }
|
19
|
+
let(:attributes_hash) { { input: input, reason: reason, result: result } }
|
20
|
+
let(:attributes) { double("Attributes", attributes_hash.merge(to_h: attributes_hash)) }
|
21
|
+
let(:workflow_execution) { double("Workflow Execution", workflow_type: double(name: 'test-wf')) }
|
22
|
+
let(:event) do
|
23
|
+
double("Event", attributes: attributes, event_type: event_type, workflow_execution:
|
24
|
+
workflow_execution)
|
25
|
+
end
|
26
|
+
let(:task) { double("Task", new_events: [event], events: [event]).as_null_object }
|
27
|
+
|
28
|
+
before { worker.stub(announce: nil, log: nil, activity_type: "test_activity") }
|
29
|
+
|
30
|
+
describe "processing a decision task" do
|
31
|
+
it "should only query for the configured task list" do
|
32
|
+
AWS::SimpleWorkflow::DecisionTaskCollection.any_instance.
|
33
|
+
should_receive(:poll_for_single_task).with("dtl")
|
34
|
+
worker.process_decision_task
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "handling event" do
|
38
|
+
before do
|
39
|
+
AWS::SimpleWorkflow::DecisionTaskCollection.any_instance.stub(
|
40
|
+
:poll_for_single_task).and_yield(task)
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "ActivityTaskTimedOut" do
|
44
|
+
let(:event_type) {"ActivityTaskTimedOut"}
|
45
|
+
|
46
|
+
it "should cancel the execution" do
|
47
|
+
task.should_receive :cancel_workflow_execution
|
48
|
+
worker.process_decision_task
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should notify" do
|
52
|
+
worker.should_receive :notify
|
53
|
+
worker.process_decision_task
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "ActivityTaskCompleted" do
|
58
|
+
let(:event_type) {"ActivityTaskCompleted"}
|
59
|
+
|
60
|
+
context "when requesting re-execution per seconds_until_retry" do
|
61
|
+
let(:result) { {seconds_until_retry: 321}.to_json }
|
62
|
+
|
63
|
+
it "schedules a timer event" do
|
64
|
+
task.should_receive(:start_timer).with(321)
|
65
|
+
worker.process_decision_task
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when not requesting re-execution" do
|
70
|
+
let(:result) { {outcome: "some_data"}.to_json }
|
71
|
+
|
72
|
+
it "schedules a workflow completed event" do
|
73
|
+
task.should_receive(:complete_workflow_execution).with(result: result)
|
74
|
+
worker.process_decision_task
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "WorkflowExecutionStarted" do
|
80
|
+
let(:event_type) {"WorkflowExecutionStarted"}
|
81
|
+
|
82
|
+
it "should schedule an activity task avoiding defaults" do
|
83
|
+
task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
|
84
|
+
heartbeat_timeout: :none,
|
85
|
+
input: anything,
|
86
|
+
schedule_to_close_timeout: anything,
|
87
|
+
schedule_to_start_timeout: anything,
|
88
|
+
start_to_close_timeout: anything,
|
89
|
+
task_list: "atl",
|
90
|
+
))
|
91
|
+
worker.process_decision_task
|
92
|
+
end
|
93
|
+
|
94
|
+
context "given no app in charge" do
|
95
|
+
let(:input) { ["legacy_stuff", {}].to_json }
|
96
|
+
|
97
|
+
it "should schedule an activity task for a guessed task list" do
|
98
|
+
task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
|
99
|
+
task_list: "atl"))
|
100
|
+
worker.process_decision_task
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "ActivityTaskFailed" do
|
106
|
+
let(:event_type) {"ActivityTaskFailed"}
|
107
|
+
|
108
|
+
context "without retry" do
|
109
|
+
let(:reason) { "Error" }
|
110
|
+
|
111
|
+
it "should fail" do
|
112
|
+
task.should_receive(:fail_workflow_execution)
|
113
|
+
worker.process_decision_task
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should not re-schedule the task" do
|
117
|
+
task.should_not_receive(:schedule_activity_task)
|
118
|
+
worker.process_decision_task
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "with retry" do
|
123
|
+
let(:reason) { "Retry" }
|
124
|
+
|
125
|
+
it "should not fail" do
|
126
|
+
task.should_not_receive(:fail_workflow_execution)
|
127
|
+
worker.process_decision_task
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should re-schedule the task" do
|
131
|
+
task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
|
132
|
+
heartbeat_timeout: :none,
|
133
|
+
input: input,
|
134
|
+
schedule_to_close_timeout: anything,
|
135
|
+
schedule_to_start_timeout: anything,
|
136
|
+
start_to_close_timeout: anything,
|
137
|
+
task_list: "atl",
|
138
|
+
))
|
139
|
+
worker.process_decision_task
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "TimerFired" do
|
145
|
+
let(:event_type) {"TimerFired"}
|
146
|
+
let(:attributes_hash) do
|
147
|
+
{
|
148
|
+
child_policy: 1,
|
149
|
+
execution_start_to_close_timeout: 2,
|
150
|
+
input: input,
|
151
|
+
tag_list: ["tag"],
|
152
|
+
task_list: "list",
|
153
|
+
task_start_to_close_timeout: 3,
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
context "given an interval option" do
|
158
|
+
let(:options) { {interval: 1234} }
|
159
|
+
|
160
|
+
it "should continue wiht mandatory attributes" do
|
161
|
+
task.should_receive(:continue_as_new_workflow_execution).with(hash_including(
|
162
|
+
attributes_hash))
|
163
|
+
worker.process_decision_task
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "given no interval" do
|
168
|
+
it "should re-schedule, assuming seconds_until_retry was set" do
|
169
|
+
task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
|
170
|
+
heartbeat_timeout: :none,
|
171
|
+
input: input,
|
172
|
+
schedule_to_close_timeout: anything,
|
173
|
+
schedule_to_start_timeout: anything,
|
174
|
+
start_to_close_timeout: anything,
|
175
|
+
task_list: "atl",
|
176
|
+
))
|
177
|
+
worker.process_decision_task
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "given an interval" do
|
183
|
+
let(:options) { {interval: 1234} }
|
184
|
+
|
185
|
+
events = %w(
|
186
|
+
ActivityTaskCompleted
|
187
|
+
ActivityTaskFailed
|
188
|
+
ActivityTaskTimedOut
|
189
|
+
)
|
190
|
+
|
191
|
+
events.each do |event|
|
192
|
+
describe event do
|
193
|
+
let(:event_type) { event }
|
194
|
+
|
195
|
+
it "should start a timer" do
|
196
|
+
task.should_receive(:start_timer).with(1234)
|
197
|
+
worker.process_decision_task
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe "string options for compatibility" do
|
203
|
+
let(:event_type) { "ActivityTaskCompleted" }
|
204
|
+
let(:input) { ["interval", {}].to_json }
|
205
|
+
|
206
|
+
it "should not be interpreted" do
|
207
|
+
task.should_not_receive :start_timer
|
208
|
+
worker.process_decision_task
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ntswf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-01-
|
12
|
+
date: 2014-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -58,6 +58,7 @@ files:
|
|
58
58
|
- lib/ntswf/client.rb
|
59
59
|
- lib/ntswf/base.rb
|
60
60
|
- lib/ntswf.rb
|
61
|
+
- spec/decision_worker_spec.rb
|
61
62
|
- spec/activity_worker_spec.rb
|
62
63
|
homepage: https://github.com/infopark/ntswf
|
63
64
|
licenses:
|
@@ -85,5 +86,6 @@ signing_key:
|
|
85
86
|
specification_version: 3
|
86
87
|
summary: Not That Simple Workflow
|
87
88
|
test_files:
|
89
|
+
- spec/decision_worker_spec.rb
|
88
90
|
- spec/activity_worker_spec.rb
|
89
91
|
has_rdoc:
|