dynflow 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|