dynflow 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +0 -10
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +99 -37
  6. data/Rakefile +2 -6
  7. data/doc/images/logo.png +0 -0
  8. data/dynflow.gemspec +10 -1
  9. data/examples/generate_work_for_daemon.rb +24 -0
  10. data/examples/orchestrate.rb +121 -0
  11. data/examples/run_daemon.rb +17 -0
  12. data/examples/web_console.rb +29 -0
  13. data/lib/dynflow.rb +27 -6
  14. data/lib/dynflow/action.rb +185 -77
  15. data/lib/dynflow/action/cancellable_polling.rb +18 -0
  16. data/lib/dynflow/action/finalize_phase.rb +18 -0
  17. data/lib/dynflow/action/flow_phase.rb +44 -0
  18. data/lib/dynflow/action/format.rb +46 -0
  19. data/lib/dynflow/action/missing.rb +26 -0
  20. data/lib/dynflow/action/plan_phase.rb +85 -0
  21. data/lib/dynflow/action/polling.rb +49 -0
  22. data/lib/dynflow/action/presenter.rb +51 -0
  23. data/lib/dynflow/action/progress.rb +62 -0
  24. data/lib/dynflow/action/run_phase.rb +43 -0
  25. data/lib/dynflow/action/suspended.rb +21 -0
  26. data/lib/dynflow/clock.rb +133 -0
  27. data/lib/dynflow/daemon.rb +29 -0
  28. data/lib/dynflow/execution_plan.rb +285 -33
  29. data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
  30. data/lib/dynflow/execution_plan/output_reference.rb +52 -0
  31. data/lib/dynflow/execution_plan/steps.rb +12 -0
  32. data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
  33. data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
  34. data/lib/dynflow/execution_plan/steps/error.rb +33 -0
  35. data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
  36. data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
  37. data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
  38. data/lib/dynflow/executors.rb +9 -0
  39. data/lib/dynflow/executors/abstract.rb +32 -0
  40. data/lib/dynflow/executors/parallel.rb +88 -0
  41. data/lib/dynflow/executors/parallel/core.rb +119 -0
  42. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
  43. data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
  44. data/lib/dynflow/executors/parallel/pool.rb +102 -0
  45. data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
  46. data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
  47. data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
  48. data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
  49. data/lib/dynflow/executors/parallel/worker.rb +30 -0
  50. data/lib/dynflow/executors/remote_via_socket.rb +38 -0
  51. data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
  52. data/lib/dynflow/flows.rb +13 -0
  53. data/lib/dynflow/flows/abstract.rb +36 -0
  54. data/lib/dynflow/flows/abstract_composed.rb +104 -0
  55. data/lib/dynflow/flows/atom.rb +36 -0
  56. data/lib/dynflow/flows/concurrence.rb +28 -0
  57. data/lib/dynflow/flows/sequence.rb +13 -0
  58. data/lib/dynflow/future.rb +173 -0
  59. data/lib/dynflow/listeners.rb +7 -0
  60. data/lib/dynflow/listeners/abstract.rb +13 -0
  61. data/lib/dynflow/listeners/serialization.rb +41 -0
  62. data/lib/dynflow/listeners/socket.rb +88 -0
  63. data/lib/dynflow/logger_adapters.rb +8 -0
  64. data/lib/dynflow/logger_adapters/abstract.rb +30 -0
  65. data/lib/dynflow/logger_adapters/delegator.rb +13 -0
  66. data/lib/dynflow/logger_adapters/formatters.rb +8 -0
  67. data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
  68. data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
  69. data/lib/dynflow/logger_adapters/simple.rb +59 -0
  70. data/lib/dynflow/micro_actor.rb +102 -0
  71. data/lib/dynflow/persistence.rb +53 -0
  72. data/lib/dynflow/persistence_adapters.rb +6 -0
  73. data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
  74. data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
  75. data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
  76. data/lib/dynflow/serializable.rb +66 -0
  77. data/lib/dynflow/simple_world.rb +18 -0
  78. data/lib/dynflow/stateful.rb +40 -0
  79. data/lib/dynflow/testing.rb +32 -0
  80. data/lib/dynflow/testing/assertions.rb +64 -0
  81. data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
  82. data/lib/dynflow/testing/dummy_executor.rb +29 -0
  83. data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
  84. data/lib/dynflow/testing/dummy_step.rb +19 -0
  85. data/lib/dynflow/testing/dummy_world.rb +33 -0
  86. data/lib/dynflow/testing/factories.rb +83 -0
  87. data/lib/dynflow/testing/managed_clock.rb +23 -0
  88. data/lib/dynflow/testing/mimic.rb +38 -0
  89. data/lib/dynflow/transaction_adapters.rb +9 -0
  90. data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
  91. data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
  92. data/lib/dynflow/transaction_adapters/none.rb +12 -0
  93. data/lib/dynflow/version.rb +1 -1
  94. data/lib/dynflow/web_console.rb +277 -0
  95. data/lib/dynflow/world.rb +168 -0
  96. data/test/action_test.rb +89 -11
  97. data/test/clock_test.rb +59 -0
  98. data/test/code_workflow_example.rb +382 -0
  99. data/test/execution_plan_test.rb +195 -64
  100. data/test/executor_test.rb +692 -0
  101. data/test/persistance_adapters_test.rb +173 -0
  102. data/test/test_helper.rb +316 -1
  103. data/test/testing_test.rb +148 -0
  104. data/test/web_console_test.rb +38 -0
  105. data/web/assets/javascripts/application.js +25 -0
  106. data/web/assets/stylesheets/application.css +101 -0
  107. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
  108. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
  109. data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
  110. data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
  111. data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
  112. data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
  113. data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
  114. data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
  115. data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
  116. data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
  117. data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
  118. data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
  119. data/web/assets/vendor/jquery/jquery.js +9807 -0
  120. data/web/views/flow.erb +19 -0
  121. data/web/views/flow_step.erb +31 -0
  122. data/web/views/index.erb +39 -0
  123. data/web/views/layout.erb +20 -0
  124. data/web/views/plan_step.erb +11 -0
  125. data/web/views/show.erb +54 -0
  126. metadata +250 -11
  127. data/examples/events.rb +0 -71
  128. data/examples/workflow.rb +0 -140
  129. data/lib/dynflow/bus.rb +0 -168
  130. data/lib/dynflow/dispatcher.rb +0 -36
  131. data/lib/dynflow/logger.rb +0 -34
  132. data/lib/dynflow/step.rb +0 -234
  133. data/test/bus_test.rb +0 -150
@@ -0,0 +1,12 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+
4
+ require 'dynflow/execution_plan/steps/error'
5
+ require 'dynflow/execution_plan/steps/abstract'
6
+ require 'dynflow/execution_plan/steps/abstract_flow_step'
7
+ require 'dynflow/execution_plan/steps/plan_step'
8
+ require 'dynflow/execution_plan/steps/run_step'
9
+ require 'dynflow/execution_plan/steps/finalize_step'
10
+
11
+ end
12
+ end
@@ -0,0 +1,121 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+ class Abstract < Serializable
4
+ include Algebrick::TypeCheck
5
+ include Stateful
6
+
7
+ attr_reader :execution_plan_id, :id, :state, :action_class, :action_id, :world, :started_at,
8
+ :ended_at, :execution_time, :real_time
9
+ attr_accessor :error
10
+
11
+ def initialize(execution_plan_id,
12
+ id,
13
+ state,
14
+ action_class,
15
+ action_id,
16
+ error,
17
+ world,
18
+ started_at = nil,
19
+ ended_at = nil,
20
+ execution_time = 0.0,
21
+ real_time = 0.0)
22
+
23
+ @id = id || raise(ArgumentError, 'missing id')
24
+ @execution_plan_id = Type! execution_plan_id, String
25
+ @world = Type! world, World
26
+ @error = Type! error, ExecutionPlan::Steps::Error, NilClass
27
+ @started_at = Type! started_at, Time, NilClass
28
+ @ended_at = Type! ended_at, Time, NilClass
29
+ @execution_time = Type! execution_time, Float
30
+ @real_time = Type! real_time, Float
31
+
32
+ self.state = state.to_sym
33
+
34
+ Type! action_class, Class
35
+ raise ArgumentError, 'action_class is not an child of Action' unless action_class < Action
36
+ raise ArgumentError, 'action_class must not be phase' if action_class.phase?
37
+ @action_class = action_class
38
+
39
+ @action_id = action_id || raise(ArgumentError, 'missing action_id')
40
+ end
41
+
42
+ def action_logger
43
+ @world.action_logger
44
+ end
45
+
46
+ def phase
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def persistence
51
+ world.persistence
52
+ end
53
+
54
+ def save
55
+ persistence.save_step(self)
56
+ end
57
+
58
+ def self.states
59
+ @states ||= [:pending, :running, :success, :suspended, :skipped, :error]
60
+ end
61
+
62
+ def execute(*args)
63
+ raise NotImplementedError
64
+ end
65
+
66
+ def to_s
67
+ "[#{self.class.name}:#{execution_plan_id}:#{id}]"
68
+ end
69
+
70
+ def to_hash
71
+ recursive_to_hash execution_plan_id: execution_plan_id,
72
+ id: id,
73
+ state: state,
74
+ class: self.class.to_s,
75
+ action_class: action_class.to_s,
76
+ action_id: action_id,
77
+ error: error,
78
+ started_at: time_to_str(started_at),
79
+ ended_at: time_to_str(ended_at),
80
+ execution_time: execution_time,
81
+ real_time: real_time
82
+ end
83
+
84
+ # @return [Array<[0..100], Fixnum>] the percentage of the step progress
85
+ # and the weight - how time-consuming the task is comparing the others.
86
+ # @see [Action::Progress] for more details
87
+ def progress
88
+ raise NotImplementedError, "Expected to be implemented in RunStep and FinalizeStep"
89
+ end
90
+
91
+ protected
92
+
93
+ def self.new_from_hash(hash, execution_plan_id, world)
94
+ check_class_matching hash
95
+ new execution_plan_id,
96
+ hash[:id],
97
+ hash[:state],
98
+ Action.constantize(hash[:action_class]),
99
+ hash[:action_id],
100
+ hash_to_error(hash[:error]),
101
+ world,
102
+ string_to_time(hash[:started_at]),
103
+ string_to_time(hash[:ended_at]),
104
+ hash[:execution_time],
105
+ hash[:real_time]
106
+ end
107
+
108
+ private
109
+
110
+ def with_time_calculation(&block)
111
+ start = Time.now
112
+ @started_at ||= start
113
+ block.call
114
+ ensure
115
+ @ended_at = Time.now
116
+ @execution_time += @ended_at - start
117
+ @real_time = @ended_at - @started_at
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,52 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+ class AbstractFlowStep < Abstract
4
+
5
+ def execute(*args)
6
+ return self if [:skipped, :success].include? self.state
7
+ open_action do |action|
8
+ action.indifferent_access_hash_variable_set :input, dereference(action.input)
9
+ with_time_calculation do
10
+ action.execute(*args)
11
+ end
12
+ end
13
+ end
14
+
15
+ def clone
16
+ self.class.from_hash(to_hash, execution_plan_id, world)
17
+ end
18
+
19
+
20
+ def progress
21
+ action = persistence.load_action(self)
22
+ [action.progress_done, action.progress_weight]
23
+ end
24
+
25
+ private
26
+
27
+ def open_action
28
+ action = persistence.load_action(self)
29
+ yield action
30
+ persistence.save_action(execution_plan_id, action)
31
+ save
32
+
33
+ return self
34
+ end
35
+
36
+ def dereference(input)
37
+ case input
38
+ when Hash
39
+ input.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
40
+ h.update(key => dereference(val))
41
+ end
42
+ when Array
43
+ input.map { |val| dereference(val) }
44
+ when ExecutionPlan::OutputReference
45
+ input.dereference(persistence, execution_plan_id)
46
+ else
47
+ input
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,33 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+ class Error < Serializable
4
+
5
+ attr_reader :exception_class, :message, :backtrace
6
+
7
+ def initialize(exception_class, message, backtrace)
8
+ @exception_class = exception_class
9
+ @message = message
10
+ @backtrace = backtrace
11
+ end
12
+
13
+ def self.new_from_hash(hash)
14
+ self.new(hash[:exception_class], hash[:message], hash[:backtrace])
15
+ end
16
+
17
+ def to_hash
18
+ { class: self.class.name,
19
+ exception_class: exception_class,
20
+ message: message,
21
+ backtrace: backtrace }
22
+ end
23
+
24
+ def to_s
25
+ "#{message} (#{exception_class})\n#{(backtrace || []).join("\n")}"
26
+ end
27
+
28
+ def exception
29
+ exception_class.constantize.exception(message).tap { |e| e.set_backtrace backtrace }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+ class FinalizeStep < AbstractFlowStep
4
+
5
+ def self.state_transitions
6
+ @state_transitions ||= {
7
+ pending: [:running, :skipped], # :skipped when its run_step is skipped
8
+ running: [:success, :error],
9
+ success: [:pending], # when restarting finalize phase
10
+ suspended: [],
11
+ skipped: [],
12
+ error: [:pending, :skipped] # pending when restarting finalize phase
13
+ }
14
+ end
15
+
16
+
17
+ def phase
18
+ :finalize_phase
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+ class PlanStep < Abstract
4
+ attr_reader :children
5
+
6
+ # @param [Array] children is a private API parameter
7
+ def initialize(execution_plan_id,
8
+ id,
9
+ state,
10
+ action_class,
11
+ action_id,
12
+ error,
13
+ world,
14
+ started_at = nil,
15
+ ended_at = nil,
16
+ execution_time = 0.0,
17
+ real_time = 0.0,
18
+ children = [])
19
+
20
+ super execution_plan_id, id, state, action_class, action_id, error, world, started_at,
21
+ ended_at, execution_time, real_time
22
+ children.all? { |child| Type! child, Integer }
23
+ @children = children
24
+ end
25
+
26
+ def phase
27
+ :plan_phase
28
+ end
29
+
30
+ def to_hash
31
+ super.merge recursive_to_hash(:children => children)
32
+ end
33
+
34
+ # @return [Action]
35
+ def execute(execution_plan, trigger, *args)
36
+ Type! execution_plan, ExecutionPlan
37
+ attributes = { execution_plan_id: execution_plan.id,
38
+ id: action_id,
39
+ step: self,
40
+ plan_step_id: self.id }
41
+ action = action_class.plan_phase.new(attributes, execution_plan, trigger)
42
+ persistence.save_action(execution_plan_id, action)
43
+
44
+ with_time_calculation do
45
+ action.execute(*args)
46
+ end
47
+
48
+ execution_plan.update_execution_time execution_time
49
+
50
+ persistence.save_action(execution_plan_id, action)
51
+ return action
52
+ end
53
+
54
+ def self.state_transitions
55
+ @state_transitions ||= { pending: [:running],
56
+ running: [:success, :error],
57
+ success: [],
58
+ suspended: [],
59
+ skipped: [],
60
+ error: [] }
61
+ end
62
+
63
+
64
+ def self.new_from_hash(hash, execution_plan_id, world)
65
+ check_class_matching hash
66
+ new execution_plan_id,
67
+ hash[:id],
68
+ hash[:state],
69
+ Action.constantize(hash[:action_class]),
70
+ hash[:action_id],
71
+ hash_to_error(hash[:error]),
72
+ world,
73
+ string_to_time(hash[:started_at]),
74
+ string_to_time(hash[:ended_at]),
75
+ hash[:execution_time],
76
+ hash[:real_time],
77
+ hash[:children]
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,21 @@
1
+ module Dynflow
2
+ module ExecutionPlan::Steps
3
+ class RunStep < AbstractFlowStep
4
+
5
+ def self.state_transitions
6
+ @state_transitions ||= {
7
+ pending: [:running, :skipped], # :skipped when it cannot be run because it depends on skipped step
8
+ running: [:success, :error, :suspended],
9
+ success: [:suspended], # after not-done process_update
10
+ suspended: [:running, :error], # process_update, e.g. error in setup_progress_updates
11
+ skipped: [],
12
+ error: [:skipped, :running]
13
+ }
14
+ end
15
+
16
+ def phase
17
+ :run_phase
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Dynflow
2
+ module Executors
3
+
4
+ require 'dynflow/executors/abstract'
5
+ require 'dynflow/executors/parallel'
6
+ require 'dynflow/executors/remote_via_socket'
7
+
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ module Dynflow
2
+ module Executors
3
+ class Abstract
4
+ include Algebrick::TypeCheck
5
+ attr_reader :world, :logger
6
+
7
+ def initialize(world)
8
+ @world = Type! world, World
9
+ @logger = world.logger
10
+ end
11
+
12
+ # @return [Future]
13
+ # @raise when execution_plan_id is not accepted
14
+ def execute(execution_plan_id)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def event(execution_plan_id, step_id, event, future = Future)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def terminate(future = Future.new)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # @return [Future]
27
+ def initialized
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ module Dynflow
2
+ module Executors
3
+ class Parallel < Abstract
4
+
5
+ require 'dynflow/executors/parallel/sequence_cursor'
6
+ require 'dynflow/executors/parallel/flow_manager'
7
+ require 'dynflow/executors/parallel/work_queue'
8
+ require 'dynflow/executors/parallel/execution_plan_manager'
9
+ require 'dynflow/executors/parallel/sequential_manager'
10
+ require 'dynflow/executors/parallel/running_steps_manager'
11
+ require 'dynflow/executors/parallel/core'
12
+ require 'dynflow/executors/parallel/pool'
13
+ require 'dynflow/executors/parallel/worker'
14
+
15
+ UnprocessableEvent = Class.new(Dynflow::Error)
16
+
17
+ # actor messages
18
+ Algebrick.types do
19
+ Boolean = type { variants TrueClass, FalseClass }
20
+
21
+ Execution = type do
22
+ fields! execution_plan_id: String,
23
+ finished: Future
24
+ end
25
+
26
+ Event = type do
27
+ fields! execution_plan_id: String,
28
+ step_id: Fixnum,
29
+ event: Object,
30
+ result: Future
31
+ end
32
+
33
+ Work = type do |work|
34
+ work::Finalize = type do
35
+ fields! sequential_manager: SequentialManager,
36
+ execution_plan_id: String
37
+ end
38
+
39
+ work::Step = type do
40
+ fields! step: ExecutionPlan::Steps::AbstractFlowStep,
41
+ execution_plan_id: String
42
+ end
43
+
44
+ work::Event = type do
45
+ fields! step: ExecutionPlan::Steps::AbstractFlowStep,
46
+ execution_plan_id: String,
47
+ event: Event
48
+ end
49
+
50
+ variants work::Step, work::Event, work::Finalize
51
+ end
52
+
53
+ PoolDone = type do
54
+ fields! work: Work
55
+ end
56
+ WorkerDone = type do
57
+ fields! work: Work, worker: Worker
58
+ end
59
+ end
60
+
61
+ def initialize(world, pool_size = 10)
62
+ super(world)
63
+ @core = Core.new world, pool_size
64
+ end
65
+
66
+ def execute(execution_plan_id, finished = Future.new)
67
+ @core.ask(Execution[execution_plan_id, finished]).value!
68
+ finished
69
+ rescue => e
70
+ finished.fail e unless finished.ready?
71
+ raise e
72
+ end
73
+
74
+ def event(execution_plan_id, step_id, event, future = Future.new)
75
+ @core << Event[execution_plan_id, step_id, event, future]
76
+ future
77
+ end
78
+
79
+ def terminate(future = Future.new)
80
+ @core.ask(MicroActor::Terminate, future)
81
+ end
82
+
83
+ def initialized
84
+ @core.initialized
85
+ end
86
+ end
87
+ end
88
+ end