dynflow 0.8.16 → 0.8.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -0
  3. data/.rubocop_todo.yml +0 -25
  4. data/doc/pages/plugins/div_tag.rb +1 -1
  5. data/doc/pages/plugins/tags.rb +0 -1
  6. data/examples/orchestrate.rb +0 -1
  7. data/examples/remote_executor.rb +3 -3
  8. data/examples/sub_plan_concurrency_control.rb +0 -1
  9. data/examples/sub_plans.rb +0 -1
  10. data/lib/dynflow.rb +1 -0
  11. data/lib/dynflow/action.rb +6 -6
  12. data/lib/dynflow/config.rb +2 -2
  13. data/lib/dynflow/connectors/database.rb +1 -1
  14. data/lib/dynflow/connectors/direct.rb +1 -1
  15. data/lib/dynflow/coordinator.rb +4 -4
  16. data/lib/dynflow/director.rb +190 -0
  17. data/lib/dynflow/director/execution_plan_manager.rb +107 -0
  18. data/lib/dynflow/director/flow_manager.rb +43 -0
  19. data/lib/dynflow/director/running_steps_manager.rb +79 -0
  20. data/lib/dynflow/director/sequence_cursor.rb +91 -0
  21. data/lib/dynflow/{executors/parallel → director}/sequential_manager.rb +2 -2
  22. data/lib/dynflow/director/work_queue.rb +48 -0
  23. data/lib/dynflow/dispatcher/client_dispatcher.rb +24 -24
  24. data/lib/dynflow/dispatcher/executor_dispatcher.rb +1 -1
  25. data/lib/dynflow/execution_plan.rb +32 -15
  26. data/lib/dynflow/execution_plan/steps/abstract.rb +14 -14
  27. data/lib/dynflow/execution_plan/steps/error.rb +1 -1
  28. data/lib/dynflow/execution_plan/steps/finalize_step.rb +0 -1
  29. data/lib/dynflow/execution_plan/steps/plan_step.rb +11 -12
  30. data/lib/dynflow/execution_plan/steps/run_step.rb +1 -1
  31. data/lib/dynflow/executors/abstract.rb +5 -8
  32. data/lib/dynflow/executors/parallel.rb +4 -34
  33. data/lib/dynflow/executors/parallel/core.rb +18 -118
  34. data/lib/dynflow/executors/parallel/pool.rb +2 -2
  35. data/lib/dynflow/executors/parallel/worker.rb +3 -11
  36. data/lib/dynflow/persistence_adapters/sequel.rb +1 -2
  37. data/lib/dynflow/testing.rb +2 -0
  38. data/lib/dynflow/testing/in_thread_executor.rb +52 -0
  39. data/lib/dynflow/testing/in_thread_world.rb +64 -0
  40. data/lib/dynflow/testing/managed_clock.rb +1 -1
  41. data/lib/dynflow/throttle_limiter.rb +1 -1
  42. data/lib/dynflow/version.rb +1 -1
  43. data/lib/dynflow/world.rb +13 -7
  44. data/test/abnormal_states_recovery_test.rb +10 -0
  45. data/test/action_test.rb +9 -9
  46. data/test/clock_test.rb +0 -2
  47. data/test/concurrency_control_test.rb +1 -1
  48. data/test/execution_plan_test.rb +0 -2
  49. data/test/executor_test.rb +6 -13
  50. data/test/support/code_workflow_example.rb +1 -1
  51. data/test/support/rescue_example.rb +0 -1
  52. data/test/test_helper.rb +9 -12
  53. data/test/testing_test.rb +74 -2
  54. data/web/views/plan_step.erb +2 -0
  55. metadata +11 -8
  56. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +0 -111
  57. data/lib/dynflow/executors/parallel/flow_manager.rb +0 -45
  58. data/lib/dynflow/executors/parallel/running_steps_manager.rb +0 -81
  59. data/lib/dynflow/executors/parallel/sequence_cursor.rb +0 -97
  60. data/lib/dynflow/executors/parallel/work_queue.rb +0 -50
@@ -28,7 +28,7 @@ module Support
28
28
  triages = all_planned_actions(Triage)
29
29
  assignees = triages.map do |triage|
30
30
  triage.output[:classification] &&
31
- triage.output[:classification][:assignee]
31
+ triage.output[:classification][:assignee]
32
32
  end.compact.uniq
33
33
  { assignees: assignees }
34
34
  end
@@ -97,6 +97,5 @@ module Support
97
97
 
98
98
  end
99
99
 
100
-
101
100
  end
102
101
  end
data/test/test_helper.rb CHANGED
@@ -97,8 +97,8 @@ module WorldFactory
97
97
  end
98
98
 
99
99
  # The worlds created by this method are getting terminated after each test run
100
- def self.create_world(&block)
101
- Dynflow::World.new(test_world_config(&block)).tap do |world|
100
+ def self.create_world(klass = Dynflow::World, &block)
101
+ klass.new(test_world_config(&block)).tap do |world|
102
102
  created_worlds << world
103
103
  end
104
104
  end
@@ -194,7 +194,7 @@ module TestHelpers
194
194
  client_world.persistence.load_execution_plan(triggered.id).state == :running
195
195
  end
196
196
 
197
- executor = WorldFactory.created_worlds.find { |e| e.id == executor_id }
197
+ executor = WorldFactory.created_worlds.find { |e| e.id == executor_id }
198
198
  raise "Could not find an executor with id #{executor_id}" unless executor
199
199
  yield executor
200
200
  return triggered
@@ -214,10 +214,10 @@ module TestHelpers
214
214
  assert_equal :stopped, plan.state
215
215
  assert_equal :success, plan.result
216
216
  assert_equal plan.execution_history.map(&:name),
217
- ['start execution',
218
- 'terminate execution',
219
- 'start execution',
220
- 'finish execution']
217
+ ['start execution',
218
+ 'terminate execution',
219
+ 'start execution',
220
+ 'finish execution']
221
221
  refute_equal plan.execution_history.first.world_id, plan.execution_history.to_a.last.world_id
222
222
  end
223
223
  end
@@ -239,7 +239,7 @@ events_test = -> do
239
239
 
240
240
  Concurrent::Edge::Event.singleton_class.send :define_method, :new do |*args, &block|
241
241
  super(*args, &block).tap do |event|
242
- event_creations[event.object_id] = caller(4)
242
+ event_creations[event.object_id] = caller(4)
243
243
  end
244
244
  end
245
245
 
@@ -332,10 +332,7 @@ module PlanAssertions
332
332
  end
333
333
 
334
334
  def assert_planning_success(execution_plan)
335
- plan_steps = execution_plan.steps.values.find_all do |step|
336
- step.is_a? Dynflow::ExecutionPlan::Steps::PlanStep
337
- end
338
- plan_steps.all? { |plan_step| plan_step.state.must_equal :success, plan_step.error }
335
+ execution_plan.plan_steps.all? { |plan_step| plan_step.state.must_equal :success, plan_step.error }
339
336
  end
340
337
 
341
338
  def assert_run_flow(expected, execution_plan)
data/test/testing_test.rb CHANGED
@@ -74,7 +74,6 @@ module Dynflow
74
74
 
75
75
  5.times { progress_action_time action }
76
76
 
77
-
78
77
  action.output.must_equal('task' => { 'progress' => 100, 'done' => true },
79
78
  'poll_attempts' => {'total' => 9, 'failed' => 0 })
80
79
  action.run_progress.must_equal 1
@@ -166,6 +165,79 @@ module Dynflow
166
165
  end
167
166
  end
168
167
  end
169
- end
170
168
 
169
+ describe "in thread executor" do
170
+ let :world do
171
+ Dynflow::Testing::InThreadWorld.instance
172
+ end
173
+
174
+ let :issues_data do
175
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
176
+ { 'author' => 'John Doe', 'text' => 'Internal server error' }]
177
+ end
178
+
179
+ let :failing_issues_data do
180
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
181
+ { 'author' => 'John Doe', 'text' => 'trolling' }]
182
+ end
183
+
184
+ let :finalize_failing_issues_data do
185
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
186
+ { 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
187
+ end
188
+
189
+ let :execution_plan do
190
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
191
+ end
192
+
193
+ let :failed_execution_plan do
194
+ plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
195
+ plan = world.execute(plan.id).value
196
+ plan.state.must_equal :paused
197
+ plan
198
+ end
199
+
200
+ let :polling_execution_plan do
201
+ world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
202
+ end
203
+
204
+ it "is able to execute plans inside the thread" do
205
+ world.execute(execution_plan.id).value.tap do |plan|
206
+ plan.state.must_equal :stopped
207
+ end
208
+ end
209
+
210
+ it "is able to handle errors in the plan" do
211
+ world.execute(failed_execution_plan.id).value.tap do |plan|
212
+ plan.state.must_equal :paused
213
+ end
214
+ end
215
+
216
+ it "is able to handle when events" do
217
+ world.execute(polling_execution_plan.id).value.tap do |plan|
218
+ plan.state.must_equal :stopped
219
+ end
220
+ end
221
+
222
+ describe 'auto rescue' do
223
+ let(:world) do
224
+ WorldFactory.create_world(Dynflow::Testing::InThreadWorld) do |config|
225
+ config.auto_rescue = true
226
+ end
227
+ end
228
+
229
+ describe 'of plan with skips' do
230
+ let :execution_plan do
231
+ plan = world.plan(Support::RescueExample::ComplexActionWithSkip, :error_on_run)
232
+ world.execute(plan.id).value
233
+ end
234
+
235
+ it 'skips the action and continues automatically' do
236
+ execution_plan.state.must_equal :stopped
237
+ execution_plan.result.must_equal :warning
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
171
243
  end
@@ -4,6 +4,8 @@
4
4
  <span class="label label-<%= step_css_class(step) %>"><%= h("#{step.action_class}") %></span>
5
5
  </p>
6
6
  <div class="action">
7
+ <p><b>Started at:</b> <%= h(step.started_at) %></p>
8
+ <p><b>Ended at:</b> <%= h(step.ended_at) %> (duration <%= duration_to_s(step.real_time) %>)</p>
7
9
  <% action = step.action @plan %>
8
10
  <%= show_action_data("Input:", action.input) %>
9
11
  <%= step_error(step) %>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.16
4
+ version: 0.8.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Necas
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-10-31 00:00:00.000000000 Z
12
+ date: 2016-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -377,6 +377,13 @@ files:
377
377
  - lib/dynflow/delayed_executors/abstract_core.rb
378
378
  - lib/dynflow/delayed_executors/polling.rb
379
379
  - lib/dynflow/delayed_plan.rb
380
+ - lib/dynflow/director.rb
381
+ - lib/dynflow/director/execution_plan_manager.rb
382
+ - lib/dynflow/director/flow_manager.rb
383
+ - lib/dynflow/director/running_steps_manager.rb
384
+ - lib/dynflow/director/sequence_cursor.rb
385
+ - lib/dynflow/director/sequential_manager.rb
386
+ - lib/dynflow/director/work_queue.rb
380
387
  - lib/dynflow/dispatcher.rb
381
388
  - lib/dynflow/dispatcher/abstract.rb
382
389
  - lib/dynflow/dispatcher/client_dispatcher.rb
@@ -397,13 +404,7 @@ files:
397
404
  - lib/dynflow/executors/abstract.rb
398
405
  - lib/dynflow/executors/parallel.rb
399
406
  - lib/dynflow/executors/parallel/core.rb
400
- - lib/dynflow/executors/parallel/execution_plan_manager.rb
401
- - lib/dynflow/executors/parallel/flow_manager.rb
402
407
  - lib/dynflow/executors/parallel/pool.rb
403
- - lib/dynflow/executors/parallel/running_steps_manager.rb
404
- - lib/dynflow/executors/parallel/sequence_cursor.rb
405
- - lib/dynflow/executors/parallel/sequential_manager.rb
406
- - lib/dynflow/executors/parallel/work_queue.rb
407
408
  - lib/dynflow/executors/parallel/worker.rb
408
409
  - lib/dynflow/flows.rb
409
410
  - lib/dynflow/flows/abstract.rb
@@ -457,6 +458,8 @@ files:
457
458
  - lib/dynflow/testing/dummy_step.rb
458
459
  - lib/dynflow/testing/dummy_world.rb
459
460
  - lib/dynflow/testing/factories.rb
461
+ - lib/dynflow/testing/in_thread_executor.rb
462
+ - lib/dynflow/testing/in_thread_world.rb
460
463
  - lib/dynflow/testing/managed_clock.rb
461
464
  - lib/dynflow/testing/mimic.rb
462
465
  - lib/dynflow/throttle_limiter.rb
@@ -1,111 +0,0 @@
1
- module Dynflow
2
- module Executors
3
- class Parallel < Abstract
4
- class ExecutionPlanManager
5
- include Algebrick::TypeCheck
6
- include Algebrick::Matching
7
-
8
- attr_reader :execution_plan, :future
9
-
10
- def initialize(world, execution_plan, future)
11
- @world = Type! world, World
12
- @execution_plan = Type! execution_plan, ExecutionPlan
13
- @future = Type! future, Concurrent::Edge::Future
14
- @running_steps_manager = RunningStepsManager.new(world)
15
-
16
- unless [:planned, :paused].include? execution_plan.state
17
- raise "execution_plan is not in pending or paused state, it's #{execution_plan.state}"
18
- end
19
- execution_plan.execution_history.add('start execution', @world.id)
20
- execution_plan.update_state(:running)
21
- end
22
-
23
- def start
24
- raise "The future was already set" if @future.completed?
25
- start_run or start_finalize or finish
26
- end
27
-
28
- def prepare_next_step(step)
29
- Work::Step[step, execution_plan.id].tap do |work|
30
- @running_steps_manager.add(step, work)
31
- end
32
- end
33
-
34
- # @return [Array<Work>] of Work items to continue with
35
- def what_is_next(work)
36
- Type! work, Work
37
-
38
- compute_next_from_step =-> step do
39
- raise unless @run_manager
40
- raise if @run_manager.done?
41
-
42
- next_steps = @run_manager.what_is_next(step)
43
- if @run_manager.done?
44
- start_finalize or finish
45
- else
46
- next_steps.map { |s| prepare_next_step(s) }
47
- end
48
- end
49
-
50
- match(work,
51
- (on Work::Step.(step: ~any) | Work::Event.(step: ~any) do |step|
52
- execution_plan.steps[step.id] = step
53
- suspended, work = @running_steps_manager.done(step)
54
- unless suspended
55
- work = compute_next_from_step.call step
56
- end
57
- work
58
- end),
59
- (on Work::Finalize do
60
- raise unless @finalize_manager
61
- finish
62
- end))
63
- end
64
-
65
- def event(event)
66
- Type! event, Parallel::Event
67
- raise unless event.execution_plan_id == @execution_plan.id
68
- @running_steps_manager.event(event)
69
- end
70
-
71
- def done?
72
- (!@run_manager || @run_manager.done?) && (!@finalize_manager || @finalize_manager.done?)
73
- end
74
-
75
- def terminate
76
- @running_steps_manager.terminate
77
- end
78
-
79
- private
80
-
81
- def no_work
82
- raise "No work but not done" unless done?
83
- []
84
- end
85
-
86
- def start_run
87
- unless execution_plan.run_flow.empty?
88
- raise 'run phase already started' if @run_manager
89
- @run_manager = FlowManager.new(execution_plan, execution_plan.run_flow)
90
- @run_manager.start.map { |s| prepare_next_step(s) }.tap { |a| raise if a.empty? }
91
- end
92
- end
93
-
94
- def start_finalize
95
- unless execution_plan.finalize_flow.empty?
96
- raise 'finalize phase already started' if @finalize_manager
97
- @finalize_manager = SequentialManager.new(@world, execution_plan)
98
- Work::Finalize[@finalize_manager, execution_plan.id]
99
- end
100
- end
101
-
102
- def finish
103
- execution_plan.execution_history.add('finish execution', @world.id)
104
- @execution_plan.update_state(execution_plan.error? ? :paused : :stopped)
105
- return no_work
106
- end
107
-
108
- end
109
- end
110
- end
111
- end
@@ -1,45 +0,0 @@
1
- module Dynflow
2
- module Executors
3
- class Parallel < Abstract
4
- class FlowManager
5
- include Algebrick::TypeCheck
6
-
7
- attr_reader :execution_plan, :cursor_index
8
-
9
- def initialize(execution_plan, flow)
10
- @execution_plan = Type! execution_plan, ExecutionPlan
11
- @flow = flow
12
- @cursor_index = {}
13
- @cursor = build_root_cursor
14
- end
15
-
16
- def done?
17
- @cursor.done?
18
- end
19
-
20
- # @return [Set] of steps to continue with
21
- def what_is_next(flow_step)
22
- return [] if flow_step.state == :suspended
23
-
24
- success = flow_step.state != :error
25
- return cursor_index[flow_step.id].what_is_next(flow_step, success)
26
- end
27
-
28
- # @return [Set] of steps to continue with
29
- def start
30
- return @cursor.what_is_next.tap do |steps|
31
- raise 'invalid state' if steps.empty? && !done?
32
- end
33
- end
34
-
35
- private
36
-
37
- def build_root_cursor
38
- # the root cursor has to always run against sequence
39
- sequence = @flow.is_a?(Flows::Sequence) ? @flow : Flows::Sequence.new([@flow])
40
- return SequenceCursor.new(self, sequence, nil)
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,81 +0,0 @@
1
- module Dynflow
2
- module Executors
3
- class Parallel < Abstract
4
-
5
- # Handles the events generated while running actions, makes sure
6
- # the events are sent to the action only when in suspended state
7
- class RunningStepsManager
8
- include Algebrick::TypeCheck
9
-
10
- def initialize(world)
11
- @world = Type! world, World
12
- @running_steps = {}
13
- @events = WorkQueue.new(Integer, Work)
14
- end
15
-
16
- def terminate
17
- pending_work = @events.clear.values.flatten(1)
18
- pending_work.each do |w|
19
- if Work::Event === w
20
- w.event.result.fail UnprocessableEvent.new("dropping due to termination")
21
- end
22
- end
23
- end
24
-
25
- def add(step, work)
26
- Type! step, ExecutionPlan::Steps::RunStep
27
- @running_steps[step.id] = step
28
- # we make sure not to run any event when the step is still being executed
29
- @events.push(step.id, work)
30
- self
31
- end
32
-
33
- # @returns [Work, nil]
34
- def done(step)
35
- Type! step, ExecutionPlan::Steps::RunStep
36
- @events.shift(step.id).tap do |work|
37
- work.event.result.success true if Work::Event === work
38
- end
39
-
40
- if step.state == :suspended
41
- return true, @events.first(step.id)
42
- else
43
- while (event = @events.shift(step.id))
44
- message = "step #{step.execution_plan_id}:#{step.id} dropping event #{event.event}"
45
- @world.logger.warn message
46
- event.event.result.fail UnprocessableEvent.new(message).
47
- tap { |e| e.set_backtrace(caller) }
48
- end
49
- raise 'assert' unless @events.empty?(step.id)
50
- @running_steps.delete(step.id)
51
- return false, nil
52
- end
53
- end
54
-
55
- def try_to_terminate
56
- @running_steps.delete_if do |_, step|
57
- step.state != :running
58
- end
59
- return @running_steps.empty?
60
- end
61
-
62
- # @returns [Work, nil]
63
- def event(event)
64
- Type! event, Parallel::Event
65
-
66
- step = @running_steps[event.step_id]
67
- unless step
68
- event.result.fail UnprocessableEvent.new(
69
- 'step is not suspended, it cannot process events')
70
- return nil
71
- end
72
-
73
- can_run_event = @events.empty?(step.id)
74
- work = Work::Event[step, event.execution_plan_id, event]
75
- @events.push(step.id, work)
76
- work if can_run_event
77
- end
78
- end
79
- end
80
- end
81
- end