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,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