dynflow 0.8.16 → 0.8.17

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