dynflow 0.7.9 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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