dynflow 0.1.0 → 0.2.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 (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