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.
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