dynflow 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.travis.yml +1 -0
  2. data/lib/dynflow.rb +1 -0
  3. data/lib/dynflow/action.rb +5 -3
  4. data/lib/dynflow/action/finalize_phase.rb +3 -1
  5. data/lib/dynflow/action/plan_phase.rb +4 -2
  6. data/lib/dynflow/action/run_phase.rb +3 -1
  7. data/lib/dynflow/daemon.rb +1 -0
  8. data/lib/dynflow/execution_plan.rb +6 -4
  9. data/lib/dynflow/executors/abstract.rb +12 -0
  10. data/lib/dynflow/executors/parallel.rb +16 -35
  11. data/lib/dynflow/executors/parallel/core.rb +13 -8
  12. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +21 -29
  13. data/lib/dynflow/executors/parallel/flow_manager.rb +0 -3
  14. data/lib/dynflow/executors/parallel/running_steps_manager.rb +5 -3
  15. data/lib/dynflow/executors/parallel/sequential_manager.rb +6 -2
  16. data/lib/dynflow/executors/parallel/worker.rb +5 -4
  17. data/lib/dynflow/executors/remote_via_socket.rb +7 -2
  18. data/lib/dynflow/executors/remote_via_socket/core.rb +66 -32
  19. data/lib/dynflow/future.rb +1 -1
  20. data/lib/dynflow/listeners/abstract.rb +4 -0
  21. data/lib/dynflow/listeners/serialization.rb +42 -8
  22. data/lib/dynflow/listeners/socket.rb +49 -19
  23. data/lib/dynflow/middleware.rb +46 -0
  24. data/lib/dynflow/middleware/action.rb +9 -0
  25. data/lib/dynflow/middleware/register.rb +32 -0
  26. data/lib/dynflow/middleware/resolver.rb +63 -0
  27. data/lib/dynflow/middleware/stack.rb +29 -0
  28. data/lib/dynflow/middleware/world.rb +58 -0
  29. data/lib/dynflow/simple_world.rb +1 -0
  30. data/lib/dynflow/testing/dummy_world.rb +3 -1
  31. data/lib/dynflow/version.rb +1 -1
  32. data/lib/dynflow/web_console.rb +7 -2
  33. data/lib/dynflow/world.rb +29 -9
  34. data/test/action_test.rb +5 -6
  35. data/test/execution_plan_test.rb +10 -11
  36. data/test/executor_test.rb +152 -89
  37. data/test/middleware_test.rb +109 -0
  38. data/test/remote_via_socket_test.rb +166 -0
  39. data/test/{code_workflow_example.rb → support/code_workflow_example.rb} +39 -30
  40. data/test/support/middleware_example.rb +132 -0
  41. data/test/test_helper.rb +18 -16
  42. data/test/testing_test.rb +4 -3
  43. data/test/web_console_test.rb +1 -2
  44. 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 Dynflow
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? Dynflow::CodeWorkflowExample::Triage
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 = external_task[:progress] + 10
278
- if progress > 25 && input[:text] =~ /cancel/
279
- world.event execution_plan_id, run_step_id, Cancel
280
- end
281
- { progress: progress }
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 fail/
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.1
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