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,88 @@
1
+ module Dynflow
2
+ module Listeners
3
+ class Socket < Abstract
4
+ # TODO terminate when exiting
5
+ include Listeners::Serialization
6
+ include Algebrick::Matching
7
+
8
+ def initialize(world, socket_path)
9
+ super(world)
10
+
11
+ File.delete socket_path if File.exist? socket_path
12
+ @server = UNIXServer.new socket_path
13
+ File.chmod(0600, socket_path)
14
+
15
+ @clients = []
16
+ @client_barriers = {}
17
+ @loop = Thread.new { loop { listen } }
18
+ end
19
+
20
+ private
21
+
22
+ def listen
23
+ ios = [@server, *@clients]
24
+ reads, writes, errors = IO.select(ios, [], ios)
25
+ reads.each do |readable|
26
+ if readable == @server
27
+ add_client @server.accept
28
+ logger.info 'Client connected.'
29
+
30
+ else
31
+ match message = receive_message(readable),
32
+ (on ~Execute do |(id, uuid)|
33
+ execute(readable, id, uuid)
34
+ end),
35
+ (on NilClass.to_m do
36
+ remove_client readable
37
+ logger.info 'Client disconnected.'
38
+ end)
39
+ end
40
+ end
41
+ rescue => error
42
+ logger.fatal error
43
+ end
44
+
45
+ def execute(readable, id, uuid)
46
+ responded = false
47
+ respond = -> error = nil do
48
+ unless responded
49
+ responded = true
50
+ send_message_to_client(readable, if error
51
+ logger.error error
52
+ Failed[id, error.message]
53
+ else
54
+ Accepted[id]
55
+ end)
56
+ end
57
+ end
58
+
59
+ @world.execute(uuid,
60
+ f = Future.new do |_|
61
+ if f.resolved?
62
+ respond.call
63
+ send_message_to_client readable, Done[id, uuid]
64
+ else
65
+ respond.call f.value
66
+ end
67
+ end)
68
+ respond.call
69
+ rescue Dynflow::Error => e
70
+ respond.call e
71
+ end
72
+
73
+ def add_client(client)
74
+ @clients << client
75
+ @client_barriers[client] = Mutex.new
76
+ end
77
+
78
+ def remove_client(client)
79
+ @clients.delete client
80
+ @client_barriers.delete client
81
+ end
82
+
83
+ def send_message_to_client(client, message)
84
+ send_message client, message, @client_barriers[client]
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,8 @@
1
+ module Dynflow
2
+ module LoggerAdapters
3
+ require 'dynflow/logger_adapters/formatters'
4
+ require 'dynflow/logger_adapters/abstract'
5
+ require 'dynflow/logger_adapters/simple'
6
+ require 'dynflow/logger_adapters/delegator'
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ module Dynflow
2
+ module LoggerAdapters
3
+ class Abstract
4
+
5
+ # @returns [#fatal, #error, #warn, #info, #debug] logger object for logging errors from action execution
6
+ def action_logger
7
+ raise NotImplementedError
8
+ end
9
+
10
+ # @returns [#fatal, #error, #warn, #info, #debug] logger object for logging Dynflow errors
11
+ def dynflow_logger
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def level
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def level=(v)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ private
24
+
25
+ def apply_formatters(base, formatters)
26
+ formatters.reduce(base) { |base, formatter| formatter.new(base) }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Dynflow
2
+ module LoggerAdapters
3
+ class Delegator < Abstract
4
+
5
+ attr_reader :action_logger, :dynflow_logger
6
+
7
+ def initialize(action_logger, dynflow_logger, formatters = [Formatters::Exception])
8
+ @action_logger = apply_formatters action_logger, formatters
9
+ @dynflow_logger = apply_formatters dynflow_logger, formatters
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module Dynflow
2
+ module LoggerAdapters
3
+ module Formatters
4
+ require 'dynflow/logger_adapters/formatters/abstract'
5
+ require 'dynflow/logger_adapters/formatters/exception'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ module Dynflow
2
+ module LoggerAdapters
3
+ module Formatters
4
+ class Abstract
5
+ def initialize(base)
6
+ @base = base
7
+ end
8
+
9
+ [:fatal, :error, :warn, :info, :debug].each do |method|
10
+ define_method method do |message, &block|
11
+ if block
12
+ @base.send method, &-> { format(block.call) }
13
+ else
14
+ @base.send method, format(message)
15
+ end
16
+ end
17
+ end
18
+
19
+ def level=(v)
20
+ @base.level = v
21
+ end
22
+
23
+ def level
24
+ @base.level
25
+ end
26
+
27
+ def format(message)
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module Dynflow
2
+ module LoggerAdapters
3
+ module Formatters
4
+ class Exception < Abstract
5
+ def format(message)
6
+ if ::Exception === message
7
+ "#{message.message} (#{message.class})\n#{message.backtrace.join("\n")}"
8
+ else
9
+ message
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ require 'English'
2
+
3
+ module Dynflow
4
+ module LoggerAdapters
5
+ class Simple < Abstract
6
+ require 'logger'
7
+
8
+ attr_reader :logger, :action_logger, :dynflow_logger
9
+
10
+ def initialize(output = $stdout, level = Logger::DEBUG, formatters = [Formatters::Exception])
11
+ @logger = Logger.new(output)
12
+ @logger.level = level
13
+ @logger.formatter = method(:formatter).to_proc
14
+ @action_logger = apply_formatters ProgNameWrapper.new(@logger, ' action'), formatters
15
+ @dynflow_logger = apply_formatters ProgNameWrapper.new(@logger, 'dynflow'), formatters
16
+ end
17
+
18
+ def level
19
+ @logger.level
20
+ end
21
+
22
+ def level=(v)
23
+ @logger.level = v
24
+ end
25
+
26
+ private
27
+
28
+ def formatter(severity, datetime, prog_name, msg)
29
+ format "[%s #%d] %5s -- %s%s\n",
30
+ datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
31
+ $PID,
32
+ severity,
33
+ (prog_name ? prog_name + ': ' : ''),
34
+ msg.to_s
35
+ end
36
+
37
+ class ProgNameWrapper
38
+ def initialize(logger, prog_name)
39
+ @logger = logger
40
+ @prog_name = prog_name
41
+ end
42
+
43
+ { fatal: 4, error: 3, warn: 2, info: 1, debug: 0 }.each do |method, level|
44
+ define_method method do |message, &block|
45
+ @logger.add level, message, @prog_name, &block
46
+ end
47
+ end
48
+
49
+ def level=(v)
50
+ @logger.level = v
51
+ end
52
+
53
+ def level
54
+ @logger.level
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,102 @@
1
+ module Dynflow
2
+ class MicroActor
3
+ include Algebrick::TypeCheck
4
+ include Algebrick::Matching
5
+
6
+ attr_reader :logger, :initialized
7
+
8
+ Terminate = Algebrick.atom
9
+
10
+ def initialize(logger, *args)
11
+ @logger = logger
12
+ @initialized = Future.new
13
+ @thread = Thread.new { run *args }
14
+ Thread.pass until @mailbox
15
+ end
16
+
17
+ def <<(message)
18
+ raise 'actor terminated' if terminated?
19
+ @mailbox << [message, nil]
20
+ self
21
+ end
22
+
23
+ def ask(message, future = Future.new)
24
+ future.fail Dynflow::Error.new('actor terminated') if terminated?
25
+ @mailbox << [message, future]
26
+ future
27
+ end
28
+
29
+ def stopped?
30
+ @terminated.ready?
31
+ end
32
+
33
+ private
34
+
35
+ def delayed_initialize(*args)
36
+ end
37
+
38
+ def termination
39
+ terminate!
40
+ end
41
+
42
+ def terminating?
43
+ @terminated
44
+ end
45
+
46
+ def terminated?
47
+ terminating? && @terminated.ready?
48
+ end
49
+
50
+ def terminate!
51
+ raise unless Thread.current == @thread
52
+ @terminated.resolve true
53
+ throw Terminate
54
+ end
55
+
56
+ def on_message(message)
57
+ raise NotImplementedError
58
+ end
59
+
60
+ def receive
61
+ message, future = @mailbox.pop
62
+ #logger.debug "#{self.class} received:\n #{message}"
63
+ if message == Terminate
64
+ # TODO do not use this future to store in @terminated use one added to Terminate message
65
+ if terminating?
66
+ @terminated.do_then { future.resolve true } if future
67
+ else
68
+ @terminated = (future || Future.new)
69
+ termination
70
+ end
71
+ else
72
+ on_envelope message, future
73
+ end
74
+ rescue => error
75
+ logger.fatal error
76
+ end
77
+
78
+ def on_envelope(message, future)
79
+ if future
80
+ future.evaluate_to { on_message message }
81
+ else
82
+ on_message message
83
+ end
84
+ if future && future.failed?
85
+ logger.error future.value
86
+ end
87
+ end
88
+
89
+ def run(*args)
90
+ Thread.current.abort_on_exception = true
91
+
92
+ @mailbox = Queue.new
93
+ @terminated = nil
94
+
95
+ delayed_initialize(*args)
96
+ Thread.pass until @initialized
97
+ @initialized.resolve true
98
+
99
+ catch(Terminate) { loop { receive } }
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,53 @@
1
+ require 'dynflow/persistence_adapters'
2
+
3
+ module Dynflow
4
+
5
+ class Persistence
6
+
7
+ attr_reader :adapter
8
+
9
+ def initialize(world, persistence_adapter)
10
+ @world = world
11
+ @adapter = persistence_adapter
12
+ end
13
+
14
+ def load_action(step)
15
+ attributes = adapter.
16
+ load_action(step.execution_plan_id, step.action_id).
17
+ update(step: step)
18
+ return Action.from_hash(attributes,
19
+ step.phase,
20
+ step,
21
+ step.world)
22
+ end
23
+
24
+ def save_action(execution_plan_id, action)
25
+ adapter.save_action(execution_plan_id, action.id, action.to_hash)
26
+ end
27
+
28
+ def find_execution_plans(options)
29
+ adapter.find_execution_plans(options).map do |execution_plan_hash|
30
+ ExecutionPlan.new_from_hash(execution_plan_hash, @world)
31
+ end
32
+ end
33
+
34
+ def load_execution_plan(id)
35
+ execution_plan_hash = adapter.load_execution_plan(id)
36
+ ExecutionPlan.new_from_hash(execution_plan_hash, @world)
37
+ end
38
+
39
+ def save_execution_plan(execution_plan)
40
+ adapter.save_execution_plan(execution_plan.id, execution_plan.to_hash)
41
+ end
42
+
43
+ def load_step(execution_plan_id, step_id, world)
44
+ step_hash = adapter.load_step(execution_plan_id, step_id)
45
+ ExecutionPlan::Steps::Abstract.from_hash(step_hash, execution_plan_id, world)
46
+ end
47
+
48
+ def save_step(step)
49
+ adapter.save_step(step.execution_plan_id, step.id, step.to_hash)
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ module Dynflow
2
+ module PersistenceAdapters
3
+ require 'dynflow/persistence_adapters/abstract'
4
+ require 'dynflow/persistence_adapters/sequel'
5
+ end
6
+ end
@@ -0,0 +1,56 @@
1
+ module Dynflow
2
+ module PersistenceAdapters
3
+ class Abstract
4
+ def pagination?
5
+ false
6
+ end
7
+
8
+ def filtering_by
9
+ []
10
+ end
11
+
12
+ def ordering_by
13
+ []
14
+ end
15
+
16
+ # @option options [Integer] page index of the page (starting at 0)
17
+ # @option options [Integer] per_page the number of the items on page
18
+ # @option options [Symbol] order_by name of the column to use for ordering
19
+ # @option options [true, false] desc set to true if order should be descending
20
+ # @option options [Hash{ Symbol => Object,Array<object> }] filters hash represents
21
+ # set of allowed values for a given key representing column
22
+ def find_execution_plans(options = {})
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def load_execution_plan(execution_plan_id)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def save_execution_plan(execution_plan_id, value)
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def load_step(execution_plan_id, step_id)
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def save_step(execution_plan_id, step_id, value)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def load_action(execution_plan_id, action_id)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def save_action(execution_plan_id, action_id, value)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ # for debug purposes
51
+ def to_hash
52
+ raise NotImplementedError
53
+ end
54
+ end
55
+ end
56
+ end