dynflow 0.2.0 → 0.3.0
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/.travis.yml +1 -0
- data/lib/dynflow.rb +1 -0
- data/lib/dynflow/action.rb +5 -3
- data/lib/dynflow/action/finalize_phase.rb +3 -1
- data/lib/dynflow/action/plan_phase.rb +4 -2
- data/lib/dynflow/action/run_phase.rb +3 -1
- data/lib/dynflow/daemon.rb +1 -0
- data/lib/dynflow/execution_plan.rb +6 -4
- data/lib/dynflow/executors/abstract.rb +12 -0
- data/lib/dynflow/executors/parallel.rb +16 -35
- data/lib/dynflow/executors/parallel/core.rb +13 -8
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +21 -29
- data/lib/dynflow/executors/parallel/flow_manager.rb +0 -3
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +5 -3
- data/lib/dynflow/executors/parallel/sequential_manager.rb +6 -2
- data/lib/dynflow/executors/parallel/worker.rb +5 -4
- data/lib/dynflow/executors/remote_via_socket.rb +7 -2
- data/lib/dynflow/executors/remote_via_socket/core.rb +66 -32
- data/lib/dynflow/future.rb +1 -1
- data/lib/dynflow/listeners/abstract.rb +4 -0
- data/lib/dynflow/listeners/serialization.rb +42 -8
- data/lib/dynflow/listeners/socket.rb +49 -19
- data/lib/dynflow/middleware.rb +46 -0
- data/lib/dynflow/middleware/action.rb +9 -0
- data/lib/dynflow/middleware/register.rb +32 -0
- data/lib/dynflow/middleware/resolver.rb +63 -0
- data/lib/dynflow/middleware/stack.rb +29 -0
- data/lib/dynflow/middleware/world.rb +58 -0
- data/lib/dynflow/simple_world.rb +1 -0
- data/lib/dynflow/testing/dummy_world.rb +3 -1
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +7 -2
- data/lib/dynflow/world.rb +29 -9
- data/test/action_test.rb +5 -6
- data/test/execution_plan_test.rb +10 -11
- data/test/executor_test.rb +152 -89
- data/test/middleware_test.rb +109 -0
- data/test/remote_via_socket_test.rb +166 -0
- data/test/{code_workflow_example.rb → support/code_workflow_example.rb} +39 -30
- data/test/support/middleware_example.rb +132 -0
- data/test/test_helper.rb +18 -16
- data/test/testing_test.rb +4 -3
- data/test/web_console_test.rb +1 -2
- metadata +16 -4
@@ -0,0 +1,109 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module Dynflow
|
4
|
+
module MiddlewareTest
|
5
|
+
|
6
|
+
describe 'Middleware' do
|
7
|
+
let(:world) { WorldInstance.world }
|
8
|
+
let(:log) { Support::MiddlewareExample::LogMiddleware.log }
|
9
|
+
|
10
|
+
before do
|
11
|
+
Support::MiddlewareExample::LogMiddleware.reset_log
|
12
|
+
end
|
13
|
+
|
14
|
+
it "wraps the action method calls" do
|
15
|
+
world.trigger(Support::MiddlewareExample::LoggingAction, {}).finished.wait
|
16
|
+
log.must_equal %w[LogMiddleware::before_plan_phase
|
17
|
+
LogMiddleware::before_plan
|
18
|
+
plan
|
19
|
+
LogMiddleware::after_plan
|
20
|
+
LogMiddleware::after_plan_phase
|
21
|
+
LogMiddleware::before_run
|
22
|
+
run
|
23
|
+
LogMiddleware::after_run
|
24
|
+
LogMiddleware::before_finalize_phase
|
25
|
+
LogMiddleware::before_finalize
|
26
|
+
finalize
|
27
|
+
LogMiddleware::after_finalize
|
28
|
+
LogMiddleware::after_finalize_phase]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "inherits the middleware" do
|
32
|
+
world.trigger(Support::MiddlewareExample::SubAction, {}).finished.wait
|
33
|
+
log.must_equal %w[LogRunMiddleware::before_run
|
34
|
+
AnotherLogRunMiddleware::before_run
|
35
|
+
run
|
36
|
+
AnotherLogRunMiddleware::after_run
|
37
|
+
LogRunMiddleware::after_run]
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "world.middleware" do
|
41
|
+
let(:world_with_middleware) do
|
42
|
+
WorldInstance.create_world.tap do |world|
|
43
|
+
world.middleware.use(Support::MiddlewareExample::AnotherLogRunMiddleware)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "puts the middleware to the beginning of the stack" do
|
48
|
+
world_with_middleware.trigger(Support::MiddlewareExample::Action, {}).finished.wait
|
49
|
+
log.must_equal %w[AnotherLogRunMiddleware::before_run
|
50
|
+
LogRunMiddleware::before_run
|
51
|
+
run
|
52
|
+
LogRunMiddleware::after_run
|
53
|
+
AnotherLogRunMiddleware::after_run]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "rules" do
|
58
|
+
describe "before" do
|
59
|
+
specify do
|
60
|
+
world.trigger(Support::MiddlewareExample::SubActionBeforeRule, {}).finished.wait
|
61
|
+
log.must_equal %w[AnotherLogRunMiddleware::before_run
|
62
|
+
LogRunMiddleware::before_run
|
63
|
+
run
|
64
|
+
LogRunMiddleware::after_run
|
65
|
+
AnotherLogRunMiddleware::after_run]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "after" do
|
70
|
+
let(:world_with_middleware) do
|
71
|
+
WorldInstance.create_world.tap do |world|
|
72
|
+
world.middleware.use(Support::MiddlewareExample::AnotherLogRunMiddleware,
|
73
|
+
after: Support::MiddlewareExample::LogRunMiddleware)
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
specify do
|
79
|
+
world_with_middleware.trigger(Support::MiddlewareExample::Action, {}).finished.wait
|
80
|
+
log.must_equal %w[LogRunMiddleware::before_run
|
81
|
+
AnotherLogRunMiddleware::before_run
|
82
|
+
run
|
83
|
+
AnotherLogRunMiddleware::after_run
|
84
|
+
LogRunMiddleware::after_run]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "replace" do
|
89
|
+
specify do
|
90
|
+
world.trigger(Support::MiddlewareExample::SubActionReplaceRule, {}).finished.wait
|
91
|
+
log.must_equal %w[AnotherLogRunMiddleware::before_run
|
92
|
+
run
|
93
|
+
AnotherLogRunMiddleware::after_run]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "allows access the running action" do
|
99
|
+
world = WorldInstance.create_world
|
100
|
+
world.middleware.use(Support::MiddlewareExample::ObservingMiddleware,
|
101
|
+
replace: Support::MiddlewareExample::LogRunMiddleware)
|
102
|
+
world.trigger(Support::MiddlewareExample::Action, message: 'hello').finished.wait
|
103
|
+
log.must_equal %w[input#message:hello
|
104
|
+
run
|
105
|
+
output#message:finished]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'remote communication' do
|
4
|
+
|
5
|
+
let(:persistence_adapter) { Dynflow::PersistenceAdapters::Sequel.new('sqlite:/') }
|
6
|
+
|
7
|
+
module Helpers
|
8
|
+
def socket_path
|
9
|
+
@socket_path ||= Dir.tmpdir + "/dynflow_remote_#{rand(1e30)}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger_adapter
|
13
|
+
WorldInstance.logger_adapter
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_world
|
17
|
+
Dynflow::SimpleWorld.new logger_adapter: logger_adapter,
|
18
|
+
auto_terminate: false,
|
19
|
+
persistence_adapter: persistence_adapter
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_remote_world
|
23
|
+
Dynflow::SimpleWorld.new(
|
24
|
+
logger_adapter: logger_adapter,
|
25
|
+
auto_terminate: false,
|
26
|
+
persistence_adapter: persistence_adapter,
|
27
|
+
executor: -> remote_world do
|
28
|
+
Dynflow::Executors::RemoteViaSocket.new(remote_world, socket_path)
|
29
|
+
end)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_listener(world)
|
33
|
+
Dynflow::Listeners::Socket.new world, socket_path, 0.05
|
34
|
+
end
|
35
|
+
|
36
|
+
def terminate(*terminable)
|
37
|
+
terminable.each { |t| t.terminate.wait }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
include Helpers
|
42
|
+
|
43
|
+
it 'raises when not connected' do
|
44
|
+
remote_world = create_remote_world
|
45
|
+
result = remote_world.trigger Support::CodeWorkflowExample::Commit, 'sha'
|
46
|
+
result.planned.must_equal true
|
47
|
+
-> { result.finished.value! }.must_raise Dynflow::Error
|
48
|
+
|
49
|
+
terminate remote_world
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'execute_planned_execution_plans' do
|
53
|
+
specify do
|
54
|
+
remote_world = create_remote_world
|
55
|
+
result = remote_world.trigger Support::CodeWorkflowExample::Commit, 'sha'
|
56
|
+
result.planned.must_equal true
|
57
|
+
-> { result.finished.value! }.must_raise Dynflow::Error
|
58
|
+
|
59
|
+
remote_world.persistence.load_execution_plan(result.id).state.must_equal :planned
|
60
|
+
|
61
|
+
world = create_world
|
62
|
+
listener = create_listener(world)
|
63
|
+
|
64
|
+
# waiting until it starts executing
|
65
|
+
assert(10.times do |i|
|
66
|
+
state = world.persistence.load_execution_plan(result.id).state
|
67
|
+
break :ok if [:running, :stopped].include? state
|
68
|
+
puts 'retry'
|
69
|
+
sleep 0.01 * i
|
70
|
+
end == :ok)
|
71
|
+
|
72
|
+
terminate remote_world, listener, world
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'shutting down' do
|
77
|
+
[:remote_world, :world, :listener].permutation.each do |order|
|
78
|
+
it "works when in order #{order}" do
|
79
|
+
objects = { world: w = create_world,
|
80
|
+
listener: create_listener(w),
|
81
|
+
remote_world: remote_world = create_remote_world }
|
82
|
+
|
83
|
+
result = remote_world.trigger Support::CodeWorkflowExample::Commit, 'sha'
|
84
|
+
result.planned.must_equal true
|
85
|
+
result.finished.value!.must_be_kind_of Dynflow::ExecutionPlan
|
86
|
+
|
87
|
+
terminate *objects.values_at(*order)
|
88
|
+
assert true, 'it has to reach this'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'allows to work others' do
|
93
|
+
world = create_world
|
94
|
+
listener = create_listener(world)
|
95
|
+
rmw1 = create_remote_world
|
96
|
+
rmw2 = create_remote_world
|
97
|
+
|
98
|
+
[rmw1.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished,
|
99
|
+
rmw2.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished].
|
100
|
+
each(&:value!)
|
101
|
+
|
102
|
+
terminate rmw1
|
103
|
+
|
104
|
+
-> { rmw1.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value! }.
|
105
|
+
must_raise Dynflow::Error
|
106
|
+
rmw2.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value!
|
107
|
+
|
108
|
+
terminate rmw2, listener, world
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'raises when disconnected while executing' do
|
112
|
+
world = create_world
|
113
|
+
listener = create_listener(world)
|
114
|
+
remote_world = create_remote_world
|
115
|
+
|
116
|
+
result = remote_world.trigger(Support::CodeWorkflowExample::Slow, 2)
|
117
|
+
result.planned.must_equal true
|
118
|
+
|
119
|
+
terminate listener
|
120
|
+
|
121
|
+
-> { result.finished.value! }.must_raise Dynflow::Future::FutureFailed
|
122
|
+
terminate remote_world, world
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'restarts' do
|
128
|
+
world = create_world
|
129
|
+
listener = create_listener(world)
|
130
|
+
remote_world = create_remote_world
|
131
|
+
|
132
|
+
remote_world.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value!
|
133
|
+
|
134
|
+
terminate listener
|
135
|
+
Thread.pass while remote_world.executor.connected?
|
136
|
+
listener = create_listener world
|
137
|
+
Thread.pass until remote_world.executor.connected?
|
138
|
+
|
139
|
+
remote_world.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value!
|
140
|
+
|
141
|
+
terminate listener, world
|
142
|
+
Thread.pass while remote_world.executor.connected?
|
143
|
+
world = create_world
|
144
|
+
listener = create_listener world
|
145
|
+
Thread.pass until remote_world.executor.connected?
|
146
|
+
|
147
|
+
remote_world.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value!
|
148
|
+
|
149
|
+
terminate listener, world, remote_world
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#connected?' do
|
153
|
+
specify do
|
154
|
+
remote_world = create_remote_world
|
155
|
+
|
156
|
+
remote_world.executor.connected?.must_equal false
|
157
|
+
|
158
|
+
world = create_world
|
159
|
+
listener = create_listener world
|
160
|
+
|
161
|
+
remote_world.executor.connected?.must_equal true
|
162
|
+
|
163
|
+
terminate listener, world, remote_world
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
|
-
module
|
3
|
+
module Support
|
4
4
|
module CodeWorkflowExample
|
5
5
|
|
6
|
-
class IncomingIssues < Action
|
6
|
+
class IncomingIssues < Dynflow::Action
|
7
7
|
|
8
8
|
def plan(issues)
|
9
9
|
issues.each do |issue|
|
@@ -25,7 +25,7 @@ module Dynflow
|
|
25
25
|
|
26
26
|
def summary
|
27
27
|
triages = all_actions.find_all do |action|
|
28
|
-
action.is_a?
|
28
|
+
action.is_a? Triage
|
29
29
|
end
|
30
30
|
assignees = triages.map do |triage|
|
31
31
|
triage.output[:classification] &&
|
@@ -36,7 +36,7 @@ module Dynflow
|
|
36
36
|
|
37
37
|
end
|
38
38
|
|
39
|
-
class Slow < Action
|
39
|
+
class Slow < Dynflow::Action
|
40
40
|
def plan(seconds)
|
41
41
|
plan_self interval: seconds
|
42
42
|
end
|
@@ -49,7 +49,7 @@ module Dynflow
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
class IncomingIssue < Action
|
52
|
+
class IncomingIssue < Dynflow::Action
|
53
53
|
|
54
54
|
def plan(issue)
|
55
55
|
plan_self(issue)
|
@@ -63,7 +63,7 @@ module Dynflow
|
|
63
63
|
|
64
64
|
end
|
65
65
|
|
66
|
-
class Triage < Action
|
66
|
+
class Triage < Dynflow::Action
|
67
67
|
|
68
68
|
def plan(issue)
|
69
69
|
triage = plan_self(issue)
|
@@ -100,7 +100,7 @@ module Dynflow
|
|
100
100
|
|
101
101
|
end
|
102
102
|
|
103
|
-
class UpdateIssue < Action
|
103
|
+
class UpdateIssue < Dynflow::Action
|
104
104
|
|
105
105
|
input_format do
|
106
106
|
param :author, String
|
@@ -113,7 +113,7 @@ module Dynflow
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
class NotifyAssignee < Action
|
116
|
+
class NotifyAssignee < Dynflow::Action
|
117
117
|
|
118
118
|
def self.subscribe
|
119
119
|
Triage
|
@@ -135,7 +135,7 @@ module Dynflow
|
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
-
class Commit < Action
|
138
|
+
class Commit < Dynflow::Action
|
139
139
|
input_format do
|
140
140
|
param :sha, String
|
141
141
|
end
|
@@ -157,7 +157,7 @@ module Dynflow
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
-
class FastCommit < Action
|
160
|
+
class FastCommit < Dynflow::Action
|
161
161
|
|
162
162
|
def plan(commit)
|
163
163
|
sequence do
|
@@ -179,7 +179,7 @@ module Dynflow
|
|
179
179
|
|
180
180
|
end
|
181
181
|
|
182
|
-
class Ci < Action
|
182
|
+
class Ci < Dynflow::Action
|
183
183
|
|
184
184
|
input_format do
|
185
185
|
param :commit, Commit.input_format
|
@@ -194,7 +194,7 @@ module Dynflow
|
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
-
class Review < Action
|
197
|
+
class Review < Dynflow::Action
|
198
198
|
|
199
199
|
input_format do
|
200
200
|
param :reviewer, String
|
@@ -214,7 +214,7 @@ module Dynflow
|
|
214
214
|
end
|
215
215
|
end
|
216
216
|
|
217
|
-
class Merge < Action
|
217
|
+
class Merge < Dynflow::Action
|
218
218
|
|
219
219
|
input_format do
|
220
220
|
param :commit, Commit.input_format
|
@@ -227,22 +227,22 @@ module Dynflow
|
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
230
|
-
class Dummy < Action
|
230
|
+
class Dummy < Dynflow::Action
|
231
231
|
end
|
232
232
|
|
233
|
-
class DummyWithFinalize < Action
|
233
|
+
class DummyWithFinalize < Dynflow::Action
|
234
234
|
def finalize
|
235
235
|
TestExecutionLog.finalize << self
|
236
236
|
end
|
237
237
|
end
|
238
238
|
|
239
|
-
class DummyTrigger < Action
|
239
|
+
class DummyTrigger < Dynflow::Action
|
240
240
|
end
|
241
241
|
|
242
|
-
class DummyAnotherTrigger < Action
|
242
|
+
class DummyAnotherTrigger < Dynflow::Action
|
243
243
|
end
|
244
244
|
|
245
|
-
class DummySubscribe < Action
|
245
|
+
class DummySubscribe < Dynflow::Action
|
246
246
|
|
247
247
|
def self.subscribe
|
248
248
|
DummyTrigger
|
@@ -253,7 +253,7 @@ module Dynflow
|
|
253
253
|
|
254
254
|
end
|
255
255
|
|
256
|
-
class DummyMultiSubscribe < Action
|
256
|
+
class DummyMultiSubscribe < Dynflow::Action
|
257
257
|
|
258
258
|
def self.subscribe
|
259
259
|
[DummyTrigger, DummyAnotherTrigger]
|
@@ -264,7 +264,7 @@ module Dynflow
|
|
264
264
|
|
265
265
|
end
|
266
266
|
|
267
|
-
class CancelableSuspended < Action
|
267
|
+
class CancelableSuspended < Dynflow::Action
|
268
268
|
include Dynflow::Action::CancellablePolling
|
269
269
|
|
270
270
|
Cancel = Dynflow::Action::CancellablePolling::Cancel
|
@@ -274,15 +274,24 @@ module Dynflow
|
|
274
274
|
end
|
275
275
|
|
276
276
|
def poll_external_task
|
277
|
-
progress
|
278
|
-
if progress
|
279
|
-
|
280
|
-
|
281
|
-
|
277
|
+
progress = external_task[:progress]
|
278
|
+
new_progress = if progress == 30
|
279
|
+
if input[:text] =~ /cancel-external/
|
280
|
+
progress
|
281
|
+
elsif input[:text] =~ /cancel-self/
|
282
|
+
world.event execution_plan_id, run_step_id, Cancel
|
283
|
+
progress
|
284
|
+
else
|
285
|
+
progress + 10
|
286
|
+
end
|
287
|
+
else
|
288
|
+
progress + 10
|
289
|
+
end
|
290
|
+
{ progress: new_progress }
|
282
291
|
end
|
283
292
|
|
284
293
|
def cancel_external_task
|
285
|
-
if input[:text] !~ /cancel
|
294
|
+
if input[:text] !~ /cancel-fail/
|
286
295
|
{ cancelled: true }
|
287
296
|
else
|
288
297
|
error! 'action cancelled'
|
@@ -302,7 +311,7 @@ module Dynflow
|
|
302
311
|
end
|
303
312
|
|
304
313
|
def poll_interval
|
305
|
-
0.
|
314
|
+
0.01
|
306
315
|
end
|
307
316
|
|
308
317
|
def run_progress
|
@@ -310,8 +319,8 @@ module Dynflow
|
|
310
319
|
end
|
311
320
|
end
|
312
321
|
|
313
|
-
class DummySuspended < Action
|
314
|
-
include Action::Polling
|
322
|
+
class DummySuspended < Dynflow::Action
|
323
|
+
include Dynflow::Action::Polling
|
315
324
|
|
316
325
|
def invoke_external_task
|
317
326
|
error! 'Trolling detected' if input[:text] == 'troll setup'
|
@@ -353,7 +362,7 @@ module Dynflow
|
|
353
362
|
end
|
354
363
|
end
|
355
364
|
|
356
|
-
class DummyHeavyProgress < Action
|
365
|
+
class DummyHeavyProgress < Dynflow::Action
|
357
366
|
|
358
367
|
def plan(input)
|
359
368
|
sequence do
|