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
data/examples/events.rb DELETED
@@ -1,71 +0,0 @@
1
- # Shows how Dynflow can be used for events architecture: actions are
2
- # subscribed to an event. When the event is triggered all the
3
- # subscribed actions are preformed.
4
-
5
- $:.unshift(File.expand_path('../../lib', __FILE__))
6
-
7
- require 'dynflow'
8
- require 'pp'
9
-
10
- # this is an event that can be triggered.
11
- # it has an input format so that the interface is given
12
- # TODO: the validations are turned off right now
13
- class Click < Dynflow::Action
14
- input_format do
15
- param :x, Integer
16
- param :y, Integer
17
- end
18
- end
19
-
20
- # SayHello subscibes to the event: it's run when the event is triggered
21
- class SayHello < Dynflow::Action
22
-
23
- def self.subscribe
24
- Click
25
- end
26
-
27
- def run
28
- puts "Hello World"
29
- end
30
- end
31
-
32
- # we can subscribe more actions to an event
33
- class SayPosition < Dynflow::Action
34
-
35
- def self.subscribe
36
- Click
37
- end
38
-
39
- def run
40
- puts "your position is [#{input['x']} - #{input['y']}]"
41
- end
42
-
43
- end
44
-
45
- # we can even subscribe to an action that is subscribed to an event
46
- class SayGoodbye < Dynflow::Action
47
-
48
- def self.subscribe
49
- SayPosition
50
- end
51
-
52
- def run
53
- puts "Good Bye"
54
- end
55
- end
56
-
57
- Click.trigger('x' => 5, 'y' => 4)
58
- # gives us:
59
- # Hello World
60
- # your position is [5 - 4]
61
- # Good Bye
62
-
63
- pp Click.plan('x' => 5, 'y' => 4).actions
64
- # returns the execution plan for the event (nothing is triggered):
65
- # [
66
- # since the event is action as well, it could have a run method
67
- # [Click: {"x"=>5, "y"=>4} ~> {},
68
- # SayHello: {"x"=>5, "y"=>4} ~> {},
69
- # SayPosition: {"x"=>5, "y"=>4} ~> {},
70
- # SayGoodbye: {"x"=>5, "y"=>4} ~> {}]
71
- # ]
data/examples/workflow.rb DELETED
@@ -1,140 +0,0 @@
1
- # Shows how Dynflow can be used for dynamic workflow definition
2
- # and execution.
3
- # In a planning phase of an action, a sub-action can be planned as
4
- # well.
5
-
6
-
7
- $:.unshift(File.expand_path('../../lib', __FILE__))
8
-
9
- require 'dynflow'
10
- require 'pp'
11
-
12
- class Article < Struct.new(:title, :body, :color); end
13
-
14
- class Publish < Dynflow::Action
15
- input_format do
16
- param :title, Integer
17
- param :body, Integer
18
- end
19
-
20
- # plan can take arbitrary arguments. The args are passed from the
21
- # trigger method.
22
- def plan(article)
23
- # we can explicitly plan a subaction
24
- plan_self 'title' => article.title, 'body' => article.body
25
- plan_action Review, article
26
- end
27
-
28
- def run
29
- puts 'Starting'
30
- end
31
-
32
- # after all actions are run, there is a finishing phase. All the
33
- # actions with +finished+ action defined are called, passing all the
34
- # performed actions (with inputs and outputs)
35
- def finalize(outputs)
36
- printer_action = outputs.find { |o| o.is_a? Print }
37
- puts "Printer says '#{printer_action.output['message']}'"
38
- end
39
- end
40
-
41
- class Review < Dynflow::Action
42
-
43
- # the actions can provide an output for the finalizing phase
44
- output_format do
45
- param :rating, Integer
46
- end
47
-
48
- # the plan method takes the same arguments as the parent action
49
- def plan(article)
50
- # in the input attribute the input for the parent action is
51
- # available
52
- plan_self input
53
- end
54
-
55
- # if no plan method given, the input is the same as the action that
56
- # triggered it
57
- def run
58
- puts "Reviewing #{input['title']}"
59
- raise "Too Short" if input['body'].size < 6
60
- output['rating'] = input['body'].size
61
- end
62
-
63
- def finalize(outputs)
64
- # +input+ and +output+ attributes are available in the finalizing
65
- # phase as well.
66
- puts "The rating was #{output['rating']}"
67
- end
68
-
69
- end
70
-
71
- class Print < Dynflow::Action
72
-
73
- input_format do
74
- param :title, Integer
75
- param :body, Integer
76
- param :color, :boolean
77
- end
78
-
79
- output_format do
80
- param :message, String
81
- end
82
-
83
- # if needed, we can subscribe to an action instead of explicitly
84
- # specifying it in the plan method. Suitable for plugin architecture.
85
- def self.subscribe
86
- Review # sucessful review means we can print
87
- end
88
-
89
- def plan(article)
90
- plan_self input.merge('color' => article.color)
91
- end
92
-
93
- def run
94
- if input['color']
95
- puts "Printing in color"
96
- else
97
- puts "Printing blank&white"
98
- end
99
- output['message'] = "Here you are"
100
- end
101
- end
102
-
103
- short_article = Article.new('Short', 'Short', false)
104
- long_article = Article.new('Long', 'This is long', false)
105
- colorful_article = Article.new('Long Color', 'This is long in color', true)
106
-
107
- pp Publish.plan(short_article).actions
108
- # the expanded workflow is:
109
- # [
110
- # Publish: {"title"=>"Short", "body"=>"Short"} ~> {},
111
- # Review: {"title"=>"Short", "body"=>"Short"} ~> {},
112
- # Print: {"title"=>"Short", "body"=>"Short", "color"=>false} ~> {}
113
- # ]
114
-
115
- begin
116
- Publish.trigger(short_article)
117
- rescue => e
118
- puts e.message
119
- end
120
- # Produces:
121
- # Starting
122
- # Reviewing Short
123
- # Too Short
124
-
125
- Publish.trigger(long_article)
126
- # Produces:
127
- # Starting
128
- # Reviewing Long
129
- # Printing blank&white
130
- # Printer says 'Here you are'
131
- # The rating was 12
132
-
133
-
134
- Publish.trigger(colorful_article)
135
- # Produces:
136
- # Starting
137
- # Reviewing Long Color
138
- # Printing in color
139
- # Printer says 'Here you are'
140
- # The rating was 21
data/lib/dynflow/bus.rb DELETED
@@ -1,168 +0,0 @@
1
- require 'active_support/inflector'
2
- require 'forwardable'
3
- module Dynflow
4
- class Bus
5
-
6
- class << self
7
- extend Forwardable
8
-
9
- def_delegators :impl, :trigger, :resume, :skip, :preview_execution_plan,
10
- :persisted_plans, :persisted_plan, :persisted_step
11
-
12
- def impl
13
- @impl ||= Bus.new
14
- end
15
- attr_writer :impl
16
- end
17
-
18
- # Entry point for running an action
19
- def trigger(action_class, *args)
20
- execution_plan = nil
21
- in_transaction_if_possible do
22
- execution_plan = prepare_execution_plan(action_class, *args)
23
- rollback_transaction if execution_plan.status == 'error'
24
- end
25
- persist_plan_if_possible(execution_plan)
26
- unless execution_plan.status == 'error'
27
- execute(execution_plan)
28
- end
29
- return execution_plan
30
- end
31
-
32
- def prepare_execution_plan(action_class, *args)
33
- action_class.plan(*args)
34
- end
35
-
36
- # execution and finalizaition. Usable for resuming paused plan
37
- # as well as starting from scratch
38
- def execute(execution_plan)
39
- run_execution_plan(execution_plan)
40
- in_transaction_if_possible do
41
- unless self.finalize(execution_plan)
42
- rollback_transaction
43
- end
44
- end
45
- execution_plan.persist(true)
46
- end
47
-
48
- alias_method :resume, :execute
49
-
50
- def skip(step)
51
- step.status = 'skipped'
52
- step.persist
53
- end
54
-
55
- # return true if everyting worked fine
56
- def finalize(execution_plan)
57
- success = true
58
- if execution_plan.run_steps.any? { |action| ['pending', 'error'].include?(action.status) }
59
- success = false
60
- else
61
- execution_plan.finalize_steps.each(&:replace_references!)
62
- execution_plan.finalize_steps.each do |step|
63
- break unless success
64
- next if %w[skipped].include?(step.status)
65
-
66
- success = step.catch_errors do
67
- step.action.finalize(execution_plan.run_steps)
68
- end
69
- end
70
- end
71
-
72
- if success
73
- execution_plan.status = 'finished'
74
- else
75
- execution_plan.status = 'paused'
76
- end
77
- return success
78
- end
79
-
80
- # return true if the run phase finished successfully
81
- def run_execution_plan(execution_plan)
82
- success = true
83
- execution_plan.run_steps.map do |step|
84
- next step if !success || %w[skipped success].include?(step.status)
85
- step.persist_before_run
86
- success = step.catch_errors do
87
- step.output = {}
88
- step.action.run
89
- end
90
- step.persist_after_run
91
- step
92
- end
93
- return success
94
- end
95
-
96
- def transaction_driver
97
- nil
98
- end
99
-
100
- def in_transaction_if_possible
101
- if transaction_driver
102
- ret = nil
103
- transaction_driver.transaction do
104
- ret = yield
105
- end
106
- return ret
107
- else
108
- return yield
109
- end
110
- end
111
-
112
- def rollback_transaction
113
- transaction_driver.rollback if transaction_driver
114
- end
115
-
116
-
117
- def persistence_driver
118
- nil
119
- end
120
-
121
- def persist_plan_if_possible(execution_plan)
122
- if persistence_driver
123
- persistence_driver.persist(execution_plan)
124
- end
125
- end
126
-
127
- def persisted_plans(status = nil)
128
- if persistence_driver
129
- persistence_driver.persisted_plans(status)
130
- else
131
- []
132
- end
133
- end
134
-
135
- def persisted_plan(persistence_id)
136
- if persistence_driver
137
- persistence_driver.persisted_plan(persistence_id)
138
- end
139
- end
140
-
141
- def persisted_step(persistence_id)
142
- if persistence_driver
143
- persistence_driver.persisted_step(persistence_id)
144
- end
145
- end
146
-
147
- # performs the planning phase of an action, but rollbacks any db
148
- # changes done in this phase. Returns the resulting execution
149
- # plan. Suitable for debugging.
150
- def preview_execution_plan(action_class, *args)
151
- unless transaction_driver
152
- raise "Bus doesn't know how to run in transaction"
153
- end
154
-
155
- execution_plan = nil
156
- transaction_driver.transaction do
157
- execution_plan = prepare_execution_plan(action_class, *args)
158
- transaction_driver.rollback
159
- end
160
- return execution_plan
161
- end
162
-
163
- def logger
164
- @logger ||= Dynflow::Logger.new(self.class)
165
- end
166
-
167
- end
168
- end
@@ -1,36 +0,0 @@
1
- module Dynflow
2
- class Dispatcher
3
- class << self
4
- def finalizers
5
- @finalizers ||= Hash.new { |h, k| h[k] = [] }
6
- end
7
-
8
- def subscribed_actions(action)
9
- Action.actions.find_all do |sub_action|
10
- case sub_action.subscribe
11
- when Hash
12
- sub_action.subscribe.keys.include?(action.class)
13
- when Array
14
- sub_action.subscribe.include?(action.class)
15
- else
16
- sub_action.subscribe == action.class
17
- end
18
- end
19
- end
20
-
21
- def execution_plan_for(action, *plan_args)
22
- ordered_actions = subscribed_actions(action).sort_by(&:name)
23
-
24
- execution_plan = ExecutionPlan.new
25
- ordered_actions.each do |action_class|
26
- sub_action_plan = action_class.plan(*plan_args) do |sub_action|
27
- sub_action.input = action.input
28
- sub_action.from_subscription = true
29
- end
30
- execution_plan.concat(sub_action_plan)
31
- end
32
- return execution_plan
33
- end
34
- end
35
- end
36
- end
@@ -1,34 +0,0 @@
1
- require 'forwardable'
2
- require 'logger'
3
-
4
- module Dynflow
5
- class Logger
6
- extend Forwardable
7
-
8
- def_delegators :@impl, :debug, :info, :warn; :error
9
-
10
- class DummyLogger < ::Logger
11
- def initialize(identifier)
12
- super(nil)
13
- end
14
- end
15
-
16
-
17
- def initialize(identifier, impl = nil)
18
- @impl = self.class.logger_class.new(identifier)
19
- end
20
-
21
- class << self
22
-
23
- def logger_class
24
- unless @logger_class
25
- @logger_class ||= DummyLogger
26
- end
27
- return @logger_class
28
- end
29
-
30
- attr_writer :logger_class
31
- end
32
-
33
- end
34
- end