dynflow 0.7.9 → 0.8.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 (118) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +16 -1
  3. data/Gemfile +13 -1
  4. data/doc/pages/source/_drafts/2015-03-01-new-documentation.markdown +10 -0
  5. data/doc/pages/source/_includes/menu.html +1 -0
  6. data/doc/pages/source/_includes/menu_right.html +1 -1
  7. data/doc/pages/source/_sass/_bootstrap-variables.sass +1 -0
  8. data/doc/pages/source/_sass/_style.scss +4 -0
  9. data/doc/pages/source/blog/index.html +12 -0
  10. data/doc/pages/source/documentation/index.md +330 -5
  11. data/dynflow.gemspec +3 -1
  12. data/examples/example_helper.rb +18 -11
  13. data/examples/orchestrate_evented.rb +2 -1
  14. data/examples/remote_executor.rb +53 -20
  15. data/lib/dynflow.rb +16 -6
  16. data/lib/dynflow/action/suspended.rb +1 -1
  17. data/lib/dynflow/action/with_sub_plans.rb +3 -6
  18. data/lib/dynflow/actor.rb +56 -0
  19. data/lib/dynflow/clock.rb +43 -38
  20. data/lib/dynflow/config.rb +107 -0
  21. data/lib/dynflow/connectors.rb +7 -0
  22. data/lib/dynflow/connectors/abstract.rb +41 -0
  23. data/lib/dynflow/connectors/database.rb +175 -0
  24. data/lib/dynflow/connectors/direct.rb +71 -0
  25. data/lib/dynflow/coordinator.rb +280 -0
  26. data/lib/dynflow/coordinator_adapters.rb +8 -0
  27. data/lib/dynflow/coordinator_adapters/abstract.rb +28 -0
  28. data/lib/dynflow/coordinator_adapters/sequel.rb +29 -0
  29. data/lib/dynflow/dispatcher.rb +58 -0
  30. data/lib/dynflow/dispatcher/abstract.rb +14 -0
  31. data/lib/dynflow/dispatcher/client_dispatcher.rb +139 -0
  32. data/lib/dynflow/dispatcher/executor_dispatcher.rb +86 -0
  33. data/lib/dynflow/errors.rb +7 -1
  34. data/lib/dynflow/execution_history.rb +46 -0
  35. data/lib/dynflow/execution_plan.rb +19 -15
  36. data/lib/dynflow/executors.rb +0 -1
  37. data/lib/dynflow/executors/abstract.rb +5 -10
  38. data/lib/dynflow/executors/parallel.rb +16 -13
  39. data/lib/dynflow/executors/parallel/core.rb +76 -78
  40. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +4 -5
  41. data/lib/dynflow/executors/parallel/pool.rb +22 -52
  42. data/lib/dynflow/executors/parallel/running_steps_manager.rb +9 -2
  43. data/lib/dynflow/executors/parallel/worker.rb +5 -10
  44. data/lib/dynflow/persistence.rb +14 -0
  45. data/lib/dynflow/persistence_adapters/abstract.rb +14 -3
  46. data/lib/dynflow/persistence_adapters/sequel.rb +142 -38
  47. data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +14 -0
  48. data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +14 -0
  49. data/lib/dynflow/round_robin.rb +37 -0
  50. data/lib/dynflow/serializable.rb +1 -2
  51. data/lib/dynflow/serializer.rb +46 -0
  52. data/lib/dynflow/testing/dummy_executor.rb +2 -2
  53. data/lib/dynflow/testing/dummy_world.rb +1 -1
  54. data/lib/dynflow/transaction_adapters/abstract.rb +0 -5
  55. data/lib/dynflow/transaction_adapters/active_record.rb +0 -10
  56. data/lib/dynflow/version.rb +1 -1
  57. data/lib/dynflow/web.rb +26 -0
  58. data/lib/dynflow/web/console.rb +108 -0
  59. data/lib/dynflow/web/console_helpers.rb +158 -0
  60. data/lib/dynflow/web/filtering_helpers.rb +85 -0
  61. data/lib/dynflow/web/world_helpers.rb +9 -0
  62. data/lib/dynflow/web_console.rb +3 -310
  63. data/lib/dynflow/world.rb +188 -119
  64. data/test/abnormal_states_recovery_test.rb +152 -0
  65. data/test/action_test.rb +2 -3
  66. data/test/clock_test.rb +1 -5
  67. data/test/coordinator_test.rb +152 -0
  68. data/test/dispatcher_test.rb +146 -0
  69. data/test/execution_plan_test.rb +2 -1
  70. data/test/executor_test.rb +534 -612
  71. data/test/middleware_test.rb +4 -4
  72. data/test/persistence_test.rb +17 -0
  73. data/test/prepare_travis_env.sh +35 -0
  74. data/test/rescue_test.rb +5 -3
  75. data/test/round_robin_test.rb +28 -0
  76. data/test/support/code_workflow_example.rb +0 -73
  77. data/test/support/dummy_example.rb +130 -0
  78. data/test/support/test_execution_log.rb +41 -0
  79. data/test/test_helper.rb +222 -116
  80. data/test/testing_test.rb +10 -10
  81. data/test/web_console_test.rb +3 -3
  82. data/test/world_test.rb +23 -0
  83. data/web/assets/images/logo-square.png +0 -0
  84. data/web/assets/stylesheets/application.css +9 -0
  85. data/web/assets/vendor/bootstrap/config.json +429 -0
  86. data/web/assets/vendor/bootstrap/css/bootstrap-theme.css +479 -0
  87. data/web/assets/vendor/bootstrap/css/bootstrap-theme.min.css +10 -0
  88. data/web/assets/vendor/bootstrap/css/bootstrap.css +5377 -4980
  89. data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -8
  90. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  91. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  92. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  93. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  94. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  95. data/web/assets/vendor/bootstrap/js/bootstrap.js +1674 -1645
  96. data/web/assets/vendor/bootstrap/js/bootstrap.min.js +11 -5
  97. data/web/views/execution_history.erb +17 -0
  98. data/web/views/index.erb +4 -6
  99. data/web/views/layout.erb +44 -8
  100. data/web/views/show.erb +4 -5
  101. data/web/views/worlds.erb +26 -0
  102. metadata +116 -23
  103. checksums.yaml +0 -15
  104. data/lib/dynflow/daemon.rb +0 -30
  105. data/lib/dynflow/executors/remote_via_socket.rb +0 -43
  106. data/lib/dynflow/executors/remote_via_socket/core.rb +0 -184
  107. data/lib/dynflow/future.rb +0 -173
  108. data/lib/dynflow/listeners.rb +0 -7
  109. data/lib/dynflow/listeners/abstract.rb +0 -17
  110. data/lib/dynflow/listeners/serialization.rb +0 -77
  111. data/lib/dynflow/listeners/socket.rb +0 -117
  112. data/lib/dynflow/micro_actor.rb +0 -102
  113. data/lib/dynflow/simple_world.rb +0 -19
  114. data/test/remote_via_socket_test.rb +0 -170
  115. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +0 -1109
  116. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +0 -9
  117. data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
  118. data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OTk4YWEzYzI4ZGUxYmEzYmRiZmYwZGUwN2I3ZjgzY2NlNmNjMDQ2OA==
5
- data.tar.gz: !binary |-
6
- OTFjZmY1OGE4MDI3MmIwY2YyYTIwYjZiOWI1YjljMTNiN2JhN2FhMA==
7
- SHA512:
8
- metadata.gz: !binary |-
9
- ZGRlZGI5Njc4ZDA4ZmFkNzMxMTdkMGM0M2E5Y2ZlNjljYjUxNzc5MDY0ZWY5
10
- MWJmNWQzYjZjOGVhMTM2ODRiZjZlM2JkMTZhY2MwMzUwNDM4M2FlN2VmZjA3
11
- ODIwODIzYjdkZGE2NGI1NGYzNzEzODQ4OTdjOWQyMTc0ZTI5YmQ=
12
- data.tar.gz: !binary |-
13
- OTllZWM0M2U2M2ZiN2I1MjA0MTQzM2ZlYzdiNzUxMzZjMTg2Mzg5ODRiOTcw
14
- MWQwNTdmMjYxN2Q3ZWFlOGRmOTVmZjJhNjIyYTNlMjZiNWI4NTc2ZWNlODg4
15
- MTUyNmMwNGY5MWFiZjc4MTU5YWRjMmEwYTllNzcwM2QzMWMxNDE=
@@ -1,30 +0,0 @@
1
- module Dynflow
2
- class Daemon
3
- include Algebrick::TypeCheck
4
-
5
- def initialize(listener, world, lock_file = nil)
6
- @listener = Type! listener, Listeners::Abstract
7
- @world = Type! world, World
8
- @lock_file = Type! lock_file, String, NilClass
9
- end
10
-
11
- def run
12
- with_lock_file do
13
- terminated = Future.new
14
- trap('SIGINT') { @world.terminate terminated }
15
- terminated.wait
16
- @listener.terminate.wait
17
- end
18
- end
19
-
20
- def with_lock_file(&block)
21
- if @lock_file
22
- raise "Lockfile #{@lock_file} is already present." if File.exist?(@lock_file)
23
- File.write(@lock_file, "Locked at #{Time.now} by process #{$$}\n")
24
- end
25
- block.call
26
- ensure
27
- File.delete(@lock_file) if @lock_file
28
- end
29
- end
30
- end
@@ -1,43 +0,0 @@
1
- require 'multi_json'
2
- require 'socket'
3
-
4
- module Dynflow
5
- module Executors
6
- class RemoteViaSocket < Abstract
7
- require 'dynflow/executors/remote_via_socket/core'
8
-
9
- include Listeners::Serialization
10
- include Algebrick::Matching
11
-
12
- def initialize(world, socket_path)
13
- super world
14
- @core = Core.new world, socket_path
15
- end
16
-
17
- def execute(execution_plan_id, finished = Future.new)
18
- @core.ask(Core::Execution[execution_plan_id, finished]).value!.value!
19
- finished
20
- rescue => e
21
- finished.fail e unless finished.ready?
22
- raise e
23
- end
24
-
25
- def event(execution_plan_id, step_id, event, future = Future)
26
- @core.ask(Core::Event[execution_plan_id, step_id, event, future]).value!
27
- future
28
- end
29
-
30
- def terminate(future = Future.new)
31
- @core.ask(MicroActor::Terminate, future)
32
- end
33
-
34
- def initialized
35
- @core.initialized
36
- end
37
-
38
- def connected?
39
- @core.ask(Core::Connect).value!
40
- end
41
- end
42
- end
43
- end
@@ -1,184 +0,0 @@
1
- module Dynflow
2
- module Executors
3
- class RemoteViaSocket < Abstract
4
- class Core < MicroActor
5
- include Listeners::Serialization
6
-
7
- Message = Algebrick.type do
8
- Job = Algebrick.type do
9
- variants Event = Executors::Abstract::Event,
10
- Execution = Executors::Abstract::Execution
11
- end
12
-
13
- variants Closed = atom,
14
- Received = type { fields message: Protocol::Response },
15
- Connect = atom,
16
- Job
17
- end
18
-
19
- TrackedJob = Algebrick.type do
20
- fields! id: Integer, job: Protocol::Job, accepted: Future, finished: Future
21
- end
22
-
23
- module TrackedJob
24
- def accept!
25
- accepted.resolve true
26
- self
27
- end
28
-
29
- def reject!(error)
30
- accepted.fail error
31
- finished.fail error
32
- self
33
- end
34
-
35
- def success!(world)
36
- raise unless accepted.ready?
37
- finished.resolve(
38
- match job,
39
- (on Core::Protocol::Execution.(execution_plan_id: ~any) do |uuid|
40
- world.persistence.load_execution_plan(uuid)
41
- end),
42
- (on Core::Protocol::Event do
43
- true
44
- end))
45
- self
46
- end
47
-
48
- def fail!(error)
49
- if accepted.ready?
50
- finished.fail error
51
- else
52
- reject! error
53
- end
54
- self
55
- end
56
- end
57
-
58
- def initialize(world, socket_path)
59
- super(world.logger, world, socket_path)
60
- end
61
-
62
- private
63
-
64
- def delayed_initialize(world, socket_path)
65
- @socket_path = Type! socket_path, String
66
- @world = Type! world, World
67
- @socket = nil
68
- @last_id = 0
69
- @tracked_jobs = {}
70
- connect
71
- end
72
-
73
- def termination
74
- terminate! if disconnect
75
- end
76
-
77
- def on_message(message)
78
- Type! message, Message
79
- match message,
80
- (on ~Job do |job|
81
- raise 'terminating' if terminating?
82
- job, future =
83
- match job,
84
- (on ~Execution do |(execution_plan_uuid, future)|
85
- [Protocol::Execution[execution_plan_uuid], future]
86
- end),
87
- (on ~Event do |(execution_plan_id, step_id, event, future)|
88
- [Protocol::Event[execution_plan_id, step_id, event], future]
89
- end)
90
- id, accepted = add_tracked_job future, job
91
- success = connect && begin
92
- send_message @socket, Protocol::Do[id, job]
93
- true
94
- rescue IOError => error
95
- logger.warn error
96
- false
97
- end
98
-
99
- unless success
100
- @tracked_jobs[id].reject!(
101
- Dynflow::Error.new(
102
- "Cannot do #{message}, no connection to a Listener"))
103
- end
104
-
105
- return accepted
106
- end),
107
-
108
- (on Received.(~Protocol::Accepted) do |(id)|
109
- @tracked_jobs[id].accept!
110
- end),
111
-
112
- (on Received.(~Protocol::Failed) do |(id, error)|
113
- @tracked_jobs.delete(id).reject! Dynflow::Error.new(error)
114
- end),
115
-
116
- (on Received.(~Protocol::Done) do |(id)|
117
- @tracked_jobs.delete(id).success! @world
118
- end),
119
-
120
- (on Closed do
121
- @socket = nil
122
- logger.info 'Disconnected from server.'
123
- @tracked_jobs.each do |_, c|
124
- c.fail! 'Connection to a Listener lost.'
125
- end
126
- @tracked_jobs.clear
127
- terminate! if terminating?
128
- end),
129
-
130
- (on Connect do
131
- connect
132
- end)
133
- end
134
-
135
- def add_tracked_job(finished, job)
136
- @tracked_jobs[id = (@last_id += 1)] = TrackedJob[id, job, accepted = Future.new, finished]
137
- return id, accepted
138
- end
139
-
140
- def connect
141
- return true if @socket
142
- @socket = UNIXSocket.new @socket_path
143
- logger.info 'Connected to server.'
144
- read_socket_until_closed
145
- true
146
- rescue SystemCallError, IOError => error
147
- logger.warn error
148
- false
149
- rescue => error
150
- logger.fatal error
151
- raise error
152
- end
153
-
154
- def disconnect
155
- return true unless @socket
156
-
157
- @socket.close
158
- false
159
- rescue Errno::ENOTCONN
160
- true
161
- end
162
-
163
- def read_socket_until_closed
164
- Thread.new do
165
- catch(:stop_reading) do
166
- loop { read_socket }
167
- end
168
- end
169
- end
170
-
171
- def read_socket
172
- match message = receive_message(@socket),
173
- Protocol::Message >-> { self << Received[message] },
174
- NilClass.to_m >-> do
175
- self << Closed
176
- throw :stop_reading
177
- end
178
- rescue => error
179
- logger.fatal error
180
- end
181
- end
182
- end
183
- end
184
- end
@@ -1,173 +0,0 @@
1
- module Dynflow
2
- class Future
3
- Error = Class.new Dynflow::Error
4
- FutureAlreadySet = Class.new Error
5
- FutureFailed = Class.new Error
6
- TimeOut = Class.new Error
7
-
8
- # `#future` will become resolved to `true` when ``#countdown!`` is called `count` times
9
- class CountDownLatch
10
- attr_reader :future
11
-
12
- def initialize(count, future = Future.new)
13
- raise ArgumentError if count < 0
14
- @count = count
15
- @lock = Mutex.new
16
- @future = future
17
- end
18
-
19
- def countdown!
20
- @lock.synchronize do
21
- @count -= 1 if @count > 0
22
- @future.resolve true if @count == 0 && !@future.ready?
23
- end
24
- end
25
-
26
- def count
27
- @lock.synchronize { @count }
28
- end
29
- end
30
-
31
- include Algebrick::TypeCheck
32
- extend Algebrick::TypeCheck
33
-
34
- def self.join(futures, result = Future.new)
35
- countdown = CountDownLatch.new(futures.size, result)
36
- futures.each do |future|
37
- Type! future, Future
38
- future.do_then { |_| countdown.countdown! }
39
- end
40
- result
41
- end
42
-
43
- def initialize(&task)
44
- @lock = Mutex.new
45
- @value = nil
46
- @resolved = false
47
- @failed = false
48
- @waiting = []
49
- @tasks = []
50
- do_then &task if task
51
- end
52
-
53
- def value(timeout = nil)
54
- wait timeout
55
- @lock.synchronize { @value }
56
- end
57
-
58
- def value!
59
- value.tap { raise value if failed? }
60
- end
61
-
62
- def resolve(result)
63
- set result, false
64
- end
65
-
66
- def fail(exception)
67
- Type! exception, Exception, String
68
- if exception.is_a? String
69
- exception = FutureFailed.new(exception).tap { |e| e.set_backtrace caller }
70
- end
71
- set exception, true
72
- end
73
-
74
- def evaluate_to(&block)
75
- resolve block.call
76
- rescue => error
77
- self.fail error
78
- end
79
-
80
- def evaluate_to!(&block)
81
- evaluate_to &block
82
- raise value if self.failed?
83
- end
84
-
85
- def do_then(&task)
86
- call_task = @lock.synchronize do
87
- @tasks << task unless _ready?
88
- @resolved
89
- end
90
- task.call value if call_task
91
- self
92
- end
93
-
94
- def set(value, failed)
95
- @lock.synchronize do
96
- raise FutureAlreadySet, "future already set to #{@value} cannot use #{value}" if _ready?
97
- if failed
98
- @failed = true
99
- else
100
- @resolved = true
101
- end
102
- @value = value
103
- while (thread = @waiting.pop)
104
- begin
105
- thread.wakeup
106
- rescue ThreadError
107
- retry
108
- end
109
- end
110
- !failed
111
- end
112
- @tasks.each { |t| t.call value }
113
- self
114
- end
115
-
116
- def wait(timeout = nil)
117
- @lock.synchronize do
118
- unless _ready?
119
- @waiting << Thread.current
120
- clock.ping self, timeout, Thread.current, :expired if timeout
121
- @lock.sleep
122
- raise TimeOut unless _ready?
123
- end
124
- end
125
- self
126
- end
127
-
128
- def ready?
129
- @lock.synchronize { _ready? }
130
- end
131
-
132
- def resolved?
133
- @lock.synchronize { @resolved }
134
- end
135
-
136
- def failed?
137
- @lock.synchronize { @failed }
138
- end
139
-
140
- def tangle(future)
141
- do_then { |v| future.set v, failed? }
142
- end
143
-
144
- # @api private
145
- def expired(thread)
146
- @lock.synchronize do
147
- thread.wakeup if @waiting.delete(thread)
148
- end
149
- end
150
-
151
- private
152
-
153
- def _ready?
154
- @resolved || @failed
155
- end
156
-
157
- @clock_barrier = Mutex.new
158
-
159
- # @api private
160
- def self.clock
161
- @clock_barrier.synchronize do
162
- # TODO remove global state and use world.clock, needs to be terminated in right order
163
- @clock ||= Clock.new(::Logger.new($stderr)).tap do |clock|
164
- at_exit { clock.ask(Clock::Terminate).wait }
165
- end
166
- end
167
- end
168
-
169
- def clock
170
- self.class.clock
171
- end
172
- end
173
- end