dynflow 0.7.9 → 0.8.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 (118) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +16 -1
  3. data/Gemfile +13 -1
  4. data/doc/pages/source/_drafts/2015-03-01-new-documentation.markdown +10 -0
  5. data/doc/pages/source/_includes/menu.html +1 -0
  6. data/doc/pages/source/_includes/menu_right.html +1 -1
  7. data/doc/pages/source/_sass/_bootstrap-variables.sass +1 -0
  8. data/doc/pages/source/_sass/_style.scss +4 -0
  9. data/doc/pages/source/blog/index.html +12 -0
  10. data/doc/pages/source/documentation/index.md +330 -5
  11. data/dynflow.gemspec +3 -1
  12. data/examples/example_helper.rb +18 -11
  13. data/examples/orchestrate_evented.rb +2 -1
  14. data/examples/remote_executor.rb +53 -20
  15. data/lib/dynflow.rb +16 -6
  16. data/lib/dynflow/action/suspended.rb +1 -1
  17. data/lib/dynflow/action/with_sub_plans.rb +3 -6
  18. data/lib/dynflow/actor.rb +56 -0
  19. data/lib/dynflow/clock.rb +43 -38
  20. data/lib/dynflow/config.rb +107 -0
  21. data/lib/dynflow/connectors.rb +7 -0
  22. data/lib/dynflow/connectors/abstract.rb +41 -0
  23. data/lib/dynflow/connectors/database.rb +175 -0
  24. data/lib/dynflow/connectors/direct.rb +71 -0
  25. data/lib/dynflow/coordinator.rb +280 -0
  26. data/lib/dynflow/coordinator_adapters.rb +8 -0
  27. data/lib/dynflow/coordinator_adapters/abstract.rb +28 -0
  28. data/lib/dynflow/coordinator_adapters/sequel.rb +29 -0
  29. data/lib/dynflow/dispatcher.rb +58 -0
  30. data/lib/dynflow/dispatcher/abstract.rb +14 -0
  31. data/lib/dynflow/dispatcher/client_dispatcher.rb +139 -0
  32. data/lib/dynflow/dispatcher/executor_dispatcher.rb +86 -0
  33. data/lib/dynflow/errors.rb +7 -1
  34. data/lib/dynflow/execution_history.rb +46 -0
  35. data/lib/dynflow/execution_plan.rb +19 -15
  36. data/lib/dynflow/executors.rb +0 -1
  37. data/lib/dynflow/executors/abstract.rb +5 -10
  38. data/lib/dynflow/executors/parallel.rb +16 -13
  39. data/lib/dynflow/executors/parallel/core.rb +76 -78
  40. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +4 -5
  41. data/lib/dynflow/executors/parallel/pool.rb +22 -52
  42. data/lib/dynflow/executors/parallel/running_steps_manager.rb +9 -2
  43. data/lib/dynflow/executors/parallel/worker.rb +5 -10
  44. data/lib/dynflow/persistence.rb +14 -0
  45. data/lib/dynflow/persistence_adapters/abstract.rb +14 -3
  46. data/lib/dynflow/persistence_adapters/sequel.rb +142 -38
  47. data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +14 -0
  48. data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +14 -0
  49. data/lib/dynflow/round_robin.rb +37 -0
  50. data/lib/dynflow/serializable.rb +1 -2
  51. data/lib/dynflow/serializer.rb +46 -0
  52. data/lib/dynflow/testing/dummy_executor.rb +2 -2
  53. data/lib/dynflow/testing/dummy_world.rb +1 -1
  54. data/lib/dynflow/transaction_adapters/abstract.rb +0 -5
  55. data/lib/dynflow/transaction_adapters/active_record.rb +0 -10
  56. data/lib/dynflow/version.rb +1 -1
  57. data/lib/dynflow/web.rb +26 -0
  58. data/lib/dynflow/web/console.rb +108 -0
  59. data/lib/dynflow/web/console_helpers.rb +158 -0
  60. data/lib/dynflow/web/filtering_helpers.rb +85 -0
  61. data/lib/dynflow/web/world_helpers.rb +9 -0
  62. data/lib/dynflow/web_console.rb +3 -310
  63. data/lib/dynflow/world.rb +188 -119
  64. data/test/abnormal_states_recovery_test.rb +152 -0
  65. data/test/action_test.rb +2 -3
  66. data/test/clock_test.rb +1 -5
  67. data/test/coordinator_test.rb +152 -0
  68. data/test/dispatcher_test.rb +146 -0
  69. data/test/execution_plan_test.rb +2 -1
  70. data/test/executor_test.rb +534 -612
  71. data/test/middleware_test.rb +4 -4
  72. data/test/persistence_test.rb +17 -0
  73. data/test/prepare_travis_env.sh +35 -0
  74. data/test/rescue_test.rb +5 -3
  75. data/test/round_robin_test.rb +28 -0
  76. data/test/support/code_workflow_example.rb +0 -73
  77. data/test/support/dummy_example.rb +130 -0
  78. data/test/support/test_execution_log.rb +41 -0
  79. data/test/test_helper.rb +222 -116
  80. data/test/testing_test.rb +10 -10
  81. data/test/web_console_test.rb +3 -3
  82. data/test/world_test.rb +23 -0
  83. data/web/assets/images/logo-square.png +0 -0
  84. data/web/assets/stylesheets/application.css +9 -0
  85. data/web/assets/vendor/bootstrap/config.json +429 -0
  86. data/web/assets/vendor/bootstrap/css/bootstrap-theme.css +479 -0
  87. data/web/assets/vendor/bootstrap/css/bootstrap-theme.min.css +10 -0
  88. data/web/assets/vendor/bootstrap/css/bootstrap.css +5377 -4980
  89. data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -8
  90. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  91. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  92. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  93. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  94. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  95. data/web/assets/vendor/bootstrap/js/bootstrap.js +1674 -1645
  96. data/web/assets/vendor/bootstrap/js/bootstrap.min.js +11 -5
  97. data/web/views/execution_history.erb +17 -0
  98. data/web/views/index.erb +4 -6
  99. data/web/views/layout.erb +44 -8
  100. data/web/views/show.erb +4 -5
  101. data/web/views/worlds.erb +26 -0
  102. metadata +116 -23
  103. checksums.yaml +0 -15
  104. data/lib/dynflow/daemon.rb +0 -30
  105. data/lib/dynflow/executors/remote_via_socket.rb +0 -43
  106. data/lib/dynflow/executors/remote_via_socket/core.rb +0 -184
  107. data/lib/dynflow/future.rb +0 -173
  108. data/lib/dynflow/listeners.rb +0 -7
  109. data/lib/dynflow/listeners/abstract.rb +0 -17
  110. data/lib/dynflow/listeners/serialization.rb +0 -77
  111. data/lib/dynflow/listeners/socket.rb +0 -117
  112. data/lib/dynflow/micro_actor.rb +0 -102
  113. data/lib/dynflow/simple_world.rb +0 -19
  114. data/test/remote_via_socket_test.rb +0 -170
  115. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +0 -1109
  116. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +0 -9
  117. data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
  118. data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
@@ -3,7 +3,6 @@ module Dynflow
3
3
 
4
4
  require 'dynflow/executors/abstract'
5
5
  require 'dynflow/executors/parallel'
6
- require 'dynflow/executors/remote_via_socket'
7
6
 
8
7
  end
9
8
  end
@@ -5,12 +5,7 @@ module Dynflow
5
5
  fields! execution_plan_id: String,
6
6
  step_id: Fixnum,
7
7
  event: Object,
8
- result: Future
9
- end
10
-
11
- Execution = Algebrick.type do
12
- fields! execution_plan_id: String,
13
- finished: Future
8
+ result: Concurrent::Edge::Future
14
9
  end
15
10
 
16
11
  include Algebrick::TypeCheck
@@ -21,21 +16,21 @@ module Dynflow
21
16
  @logger = world.logger
22
17
  end
23
18
 
24
- # @return [Future]
19
+ # @return [Concurrent::Edge::Future]
25
20
  # @raise when execution_plan_id is not accepted
26
21
  def execute(execution_plan_id)
27
22
  raise NotImplementedError
28
23
  end
29
24
 
30
- def event(execution_plan_id, step_id, event, future = Future)
25
+ def event(execution_plan_id, step_id, event, future = Concurrent.future)
31
26
  raise NotImplementedError
32
27
  end
33
28
 
34
- def terminate(future = Future.new)
29
+ def terminate(future = Concurrent.future)
35
30
  raise NotImplementedError
36
31
  end
37
32
 
38
- # @return [Future]
33
+ # @return [Concurrent::Edge::Future]
39
34
  def initialized
40
35
  raise NotImplementedError
41
36
  end
@@ -36,34 +36,37 @@ module Dynflow
36
36
  variants Work::Step, Work::Event, Work::Finalize
37
37
  end
38
38
 
39
- PoolDone = Algebrick.type { fields! work: Work }
40
- PoolTerminated = Algebrick.atom
41
- WorkerDone = Algebrick.type { fields! work: Work, worker: Worker }
42
-
43
39
  def initialize(world, pool_size = 10)
44
40
  super(world)
45
- @core = Core.new world, pool_size
41
+ @core = Core.spawn name: 'parallel-executor-core',
42
+ args: [world, pool_size],
43
+ initialized: @core_initialized = Concurrent.future
46
44
  end
47
45
 
48
- def execute(execution_plan_id, finished = Future.new)
49
- @core.ask(Execution[execution_plan_id, finished]).value!
46
+ def execute(execution_plan_id, finished = Concurrent.future)
47
+ @core.ask([:handle_execution, execution_plan_id, finished]).value!
50
48
  finished
49
+ rescue Concurrent::Actor::ActorTerminated => error
50
+ dynflow_error = Dynflow::Error.new('executor terminated')
51
+ finished.fail dynflow_error unless finished.completed?
52
+ raise dynflow_error
51
53
  rescue => e
52
- finished.fail e unless finished.ready?
54
+ finished.fail e unless finished.completed?
53
55
  raise e
54
56
  end
55
57
 
56
- def event(execution_plan_id, step_id, event, future = Future.new)
57
- @core << Event[execution_plan_id, step_id, event, future]
58
+ def event(execution_plan_id, step_id, event, future = Concurrent.future)
59
+ @core.ask([:handle_event, Event[execution_plan_id, step_id, event, future]])
58
60
  future
59
61
  end
60
62
 
61
- def terminate(future = Future.new)
62
- @core.ask(MicroActor::Terminate, future)
63
+ def terminate(future = Concurrent.future)
64
+ @core.tell([:start_termination, future])
65
+ future
63
66
  end
64
67
 
65
68
  def initialized
66
- @core.initialized
69
+ @core_initialized
67
70
  end
68
71
  end
69
72
  end
@@ -2,51 +2,83 @@ module Dynflow
2
2
  module Executors
3
3
  class Parallel < Abstract
4
4
 
5
- # TODO add dynflow error handling to avoid getting stuck and report errors to the future
6
- class Core < MicroActor
7
- def initialize(world, pool_size)
8
- super(world.logger, world, pool_size)
9
- end
10
-
11
- private
5
+ class Core < Actor
6
+ attr_reader :logger
12
7
 
13
- def delayed_initialize(world, pool_size)
8
+ def initialize(world, pool_size)
9
+ @logger = world.logger
14
10
  @world = Type! world, World
15
- @pool = Pool.new(self, pool_size, world.transaction_adapter)
11
+ @pool = Pool.spawn('pool', reference, pool_size, world.transaction_adapter)
16
12
  @execution_plan_managers = {}
17
13
  @plan_ids_in_rescue = Set.new
14
+ @terminated = nil
18
15
  end
19
16
 
20
- def on_message(message)
21
- match message,
22
- (on ~Parallel::Execution do |(execution_plan_id, finished)|
23
- start_executing track_execution_plan(execution_plan_id, finished)
24
- true
25
- end),
26
- (on ~Parallel::Event do |event|
27
- event(event)
28
- end),
29
- (on Parallel::PoolTerminated do
30
- finish_termination
31
- end),
32
- (on PoolDone.(~any) do |step|
33
- update_manager(step)
34
- end),
35
- (on ~Errors::PersistenceError.to_m do |error|
36
- logger.fatal "PersistenceError in executor: terminating"
37
- logger.fatal error
38
- @world.terminate
39
- end)
40
- rescue Errors::PersistenceError => e
41
- self << e
17
+ def handle_execution(execution_plan_id, finished)
18
+ start_executing track_execution_plan(execution_plan_id, finished)
19
+ end
20
+
21
+ def handle_event(event)
22
+ Type! event, Parallel::Event
23
+ if terminating?
24
+ raise Dynflow::Error,
25
+ "cannot accept event: #{event} core is terminating"
26
+ end
27
+ execution_plan_manager = @execution_plan_managers[event.execution_plan_id]
28
+ if execution_plan_manager
29
+ feed_pool execution_plan_manager.event(event)
30
+ true
31
+ else
32
+ raise Dynflow::Error, "no manager for #{event.inspect}"
33
+ end
34
+ rescue Dynflow::Error => e
35
+ event.result.fail e.message
36
+ raise e
42
37
  end
43
38
 
44
- def termination
39
+ def finish_step(step)
40
+ update_manager(step)
41
+ end
42
+
43
+ def handle_persistence_error(error)
44
+ logger.fatal "PersistenceError in executor: terminating"
45
+ logger.fatal error
46
+ @world.terminate
47
+ end
48
+
49
+ def start_termination(*args)
50
+ super
45
51
  logger.info 'shutting down Core ...'
46
- @pool << MicroActor::Terminate
52
+ @pool.tell([:start_termination, Concurrent.future])
53
+ end
54
+
55
+ def finish_termination
56
+ unless @execution_plan_managers.empty?
57
+ logger.error "... cleaning #{@execution_plan_managers.size} execution plans ..."
58
+ begin
59
+ @execution_plan_managers.values.each do |manager|
60
+ manager.terminate
61
+ end
62
+ rescue Errors::PersistenceError
63
+ logger.error "could not to clean the data properly"
64
+ end
65
+ @execution_plan_managers.values.each do |manager|
66
+ finish_plan(manager.execution_plan.id)
67
+ end
68
+ end
69
+ logger.error '... core terminated.'
70
+ super
71
+ end
72
+
73
+ private
74
+
75
+ def on_message(message)
76
+ super
77
+ rescue Errors::PersistenceError => e
78
+ self.tell(:handle_persistence_error, e)
47
79
  end
48
80
 
49
- # @return false on problem
81
+ # @return
50
82
  def track_execution_plan(execution_plan_id, finished)
51
83
  execution_plan = @world.persistence.load_execution_plan(execution_plan_id)
52
84
 
@@ -70,14 +102,7 @@ module Dynflow
70
102
 
71
103
  rescue Dynflow::Error => e
72
104
  finished.fail e
73
- raise e
74
- end
75
-
76
- def start_executing(manager)
77
- Type! manager, ExecutionPlanManager
78
-
79
- next_work = manager.start
80
- continue_manager manager, next_work
105
+ nil
81
106
  end
82
107
 
83
108
  def update_manager(finished_work)
@@ -106,18 +131,19 @@ module Dynflow
106
131
  @plan_ids_in_rescue << manager.execution_plan.id
107
132
  rescue_plan_id = manager.execution_plan.rescue_plan_id
108
133
  if rescue_plan_id
109
- self << Parallel::Execution[rescue_plan_id, manager.future]
134
+ reference.tell([:handle_execution, rescue_plan_id, manager.future])
110
135
  else
111
136
  set_future(manager)
112
137
  end
113
138
  end
114
139
 
115
140
  def feed_pool(work_items)
141
+ return if terminating?
116
142
  Type! work_items, Array, Work, NilClass
117
143
  return if work_items.nil?
118
144
  work_items = [work_items] if work_items.is_a? Work
119
145
  work_items.all? { |i| Type! i, Work }
120
- work_items.each { |new_work| @pool << new_work }
146
+ work_items.each { |new_work| @pool.tell([:schedule_work, new_work]) }
121
147
  end
122
148
 
123
149
  def finish_plan(execution_plan_id)
@@ -131,45 +157,17 @@ module Dynflow
131
157
 
132
158
  def set_future(manager)
133
159
  @plan_ids_in_rescue.delete(manager.execution_plan.id)
134
- manager.future.resolve manager.execution_plan
160
+ manager.future.success manager.execution_plan
135
161
  end
136
162
 
163
+ def start_executing(manager)
164
+ return if manager.nil?
165
+ Type! manager, ExecutionPlanManager
137
166
 
138
- def event(event)
139
- Type! event, Parallel::Event
140
- if terminating?
141
- raise Dynflow::Error,
142
- "cannot accept event: #{event} core is terminating"
143
- end
144
- execution_plan_manager = @execution_plan_managers[event.execution_plan_id]
145
- if execution_plan_manager
146
- feed_pool execution_plan_manager.event(event)
147
- true
148
- else
149
- raise Dynflow::Error, "no manager for #{event.execution_plan_id}:#{event.step_id}"
150
- end
151
- rescue Dynflow::Error => e
152
- event.result.fail e.message
153
- raise e
167
+ next_work = manager.start
168
+ continue_manager manager, next_work
154
169
  end
155
170
 
156
- def finish_termination
157
- unless @execution_plan_managers.empty?
158
- logger.error "... cleaning #{@execution_plan_managers.size} execution plans ..."
159
- begin
160
- @execution_plan_managers.values.each do |manager|
161
- manager.terminate
162
- end
163
- rescue Errors::PersistenceError
164
- logger.error "could not to clean the data properly"
165
- end
166
- @execution_plan_managers.values.each do |manager|
167
- finish_plan(manager.execution_plan.id)
168
- end
169
- end
170
- logger.error '... core terminated.'
171
- terminate!
172
- end
173
171
  end
174
172
  end
175
173
  end
@@ -10,17 +10,18 @@ module Dynflow
10
10
  def initialize(world, execution_plan, future)
11
11
  @world = Type! world, World
12
12
  @execution_plan = Type! execution_plan, ExecutionPlan
13
- @future = Type! future, Future
13
+ @future = Type! future, Concurrent::Edge::Future
14
14
  @running_steps_manager = RunningStepsManager.new(world)
15
15
 
16
16
  unless [:planned, :paused].include? execution_plan.state
17
17
  raise "execution_plan is not in pending or paused state, it's #{execution_plan.state}"
18
18
  end
19
+ execution_plan.execution_history.add('start execution', @world.id)
19
20
  execution_plan.update_state(:running)
20
21
  end
21
22
 
22
23
  def start
23
- raise "The future was already set" if @future.ready?
24
+ raise "The future was already set" if @future.completed?
24
25
  start_run or start_finalize or finish
25
26
  end
26
27
 
@@ -73,9 +74,6 @@ module Dynflow
73
74
 
74
75
  def terminate
75
76
  @running_steps_manager.terminate
76
- unless @execution_plan.state == :paused
77
- @execution_plan.update_state(:paused)
78
- end
79
77
  end
80
78
 
81
79
  private
@@ -102,6 +100,7 @@ module Dynflow
102
100
  end
103
101
 
104
102
  def finish
103
+ execution_plan.execution_history.add('finish execution', @world.id)
105
104
  @execution_plan.update_state(execution_plan.error? ? :paused : :stopped)
106
105
  return no_work
107
106
  end
@@ -1,33 +1,7 @@
1
1
  module Dynflow
2
2
  module Executors
3
3
  class Parallel < Abstract
4
- class Pool < MicroActor
5
- class RoundRobin
6
- def initialize
7
- @data = []
8
- @cursor = 0
9
- end
10
-
11
- def add(item)
12
- @data.push item
13
- self
14
- end
15
-
16
- def delete(item)
17
- @data.delete item
18
- self
19
- end
20
-
21
- def next
22
- @cursor = 0 if @cursor > @data.size-1
23
- @data[@cursor].tap { @cursor += 1 }
24
- end
25
-
26
- def empty?
27
- @data.empty?
28
- end
29
- end
30
-
4
+ class Pool < Actor
31
5
  class JobStorage
32
6
  def initialize
33
7
  @round_robin = RoundRobin.new
@@ -62,43 +36,39 @@ module Dynflow
62
36
  end
63
37
 
64
38
  def initialize(core, pool_size, transaction_adapter)
65
- super(core.logger, core, pool_size, transaction_adapter)
39
+ @executor_core = core
40
+ @pool_size = pool_size
41
+ @free_workers = Array.new(pool_size) { |i| Worker.spawn("worker-#{i}", reference, transaction_adapter) }
42
+ @jobs = JobStorage.new
66
43
  end
67
44
 
68
- private
45
+ def schedule_work(work)
46
+ @jobs.add work
47
+ distribute_jobs
48
+ end
69
49
 
70
- def delayed_initialize(core, pool_size, transaction_adapter)
71
- @core = core
72
- @pool_size = pool_size
73
- @free_workers = Array.new(pool_size) { Worker.new(self, transaction_adapter) }
74
- @jobs = JobStorage.new
50
+ def worker_done(worker, step)
51
+ @executor_core.tell([:finish_step, step])
52
+ @free_workers << worker
53
+ distribute_jobs
75
54
  end
76
55
 
77
- def on_message(message)
78
- match message,
79
- (on ~Work do |work|
80
- @jobs.add work
81
- distribute_jobs
82
- end),
83
- (on WorkerDone.(~any, ~any) do |step, worker|
84
- @core << PoolDone[step]
85
- @free_workers << worker
86
- distribute_jobs
87
- end),
88
- (on Errors::PersistenceError do
89
- @core << message
90
- end)
56
+ def handle_persistence_error(error)
57
+ @executor_core.tell(:handle_persistence_error, error)
91
58
  end
92
59
 
93
- def termination
60
+ def start_termination(*args)
61
+ super
94
62
  try_to_terminate
95
63
  end
96
64
 
65
+ private
66
+
97
67
  def try_to_terminate
98
68
  if terminating? && @free_workers.size == @pool_size
99
- @free_workers.map { |worker| worker.ask(Terminate) }.each(&:wait)
100
- @core << PoolTerminated
101
- terminate!
69
+ @free_workers.map { |worker| worker.ask(:terminate!) }.map(&:wait)
70
+ @executor_core.tell(:finish_termination)
71
+ finish_termination
102
72
  end
103
73
  end
104
74
 
@@ -14,7 +14,7 @@ module Dynflow
14
14
  end
15
15
 
16
16
  def terminate
17
- pending_work = @events.clear.values.flatten
17
+ pending_work = @events.clear.values.flatten(1)
18
18
  pending_work.each do |w|
19
19
  if Work::Event === w
20
20
  w.event.result.fail UnprocessableEvent.new("dropping due to termination")
@@ -34,7 +34,7 @@ module Dynflow
34
34
  def done(step)
35
35
  Type! step, ExecutionPlan::Steps::RunStep
36
36
  @events.shift(step.id).tap do |work|
37
- work.event.result.resolve true if Work::Event === work
37
+ work.event.result.success true if Work::Event === work
38
38
  end
39
39
 
40
40
  if step.state == :suspended
@@ -52,6 +52,13 @@ module Dynflow
52
52
  end
53
53
  end
54
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
+
55
62
  # @returns [Work, nil]
56
63
  def event(event)
57
64
  Type! event, Parallel::Event