ntswf 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/ntswf.png)](http://badge.fury.io/rb/ntswf)
|
10
10
|
[![Code Climate](https://codeclimate.com/github/infopark/ntswf.png)](https://codeclimate.com/github/infopark/ntswf)
|
11
11
|
[![Dependency Status](https://gemnasium.com/infopark/ntswf.png)](https://gemnasium.com/infopark/ntswf)
|
12
|
+
[![Build Status](https://travis-ci.org/infopark/ntswf.png)](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:
|