dynflow 0.2.0 → 0.3.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.
- data/.travis.yml +1 -0
- data/lib/dynflow.rb +1 -0
- data/lib/dynflow/action.rb +5 -3
- data/lib/dynflow/action/finalize_phase.rb +3 -1
- data/lib/dynflow/action/plan_phase.rb +4 -2
- data/lib/dynflow/action/run_phase.rb +3 -1
- data/lib/dynflow/daemon.rb +1 -0
- data/lib/dynflow/execution_plan.rb +6 -4
- data/lib/dynflow/executors/abstract.rb +12 -0
- data/lib/dynflow/executors/parallel.rb +16 -35
- data/lib/dynflow/executors/parallel/core.rb +13 -8
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +21 -29
- data/lib/dynflow/executors/parallel/flow_manager.rb +0 -3
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +5 -3
- data/lib/dynflow/executors/parallel/sequential_manager.rb +6 -2
- data/lib/dynflow/executors/parallel/worker.rb +5 -4
- data/lib/dynflow/executors/remote_via_socket.rb +7 -2
- data/lib/dynflow/executors/remote_via_socket/core.rb +66 -32
- data/lib/dynflow/future.rb +1 -1
- data/lib/dynflow/listeners/abstract.rb +4 -0
- data/lib/dynflow/listeners/serialization.rb +42 -8
- data/lib/dynflow/listeners/socket.rb +49 -19
- data/lib/dynflow/middleware.rb +46 -0
- data/lib/dynflow/middleware/action.rb +9 -0
- data/lib/dynflow/middleware/register.rb +32 -0
- data/lib/dynflow/middleware/resolver.rb +63 -0
- data/lib/dynflow/middleware/stack.rb +29 -0
- data/lib/dynflow/middleware/world.rb +58 -0
- data/lib/dynflow/simple_world.rb +1 -0
- data/lib/dynflow/testing/dummy_world.rb +3 -1
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +7 -2
- data/lib/dynflow/world.rb +29 -9
- data/test/action_test.rb +5 -6
- data/test/execution_plan_test.rb +10 -11
- data/test/executor_test.rb +152 -89
- data/test/middleware_test.rb +109 -0
- data/test/remote_via_socket_test.rb +166 -0
- data/test/{code_workflow_example.rb → support/code_workflow_example.rb} +39 -30
- data/test/support/middleware_example.rb +132 -0
- data/test/test_helper.rb +18 -16
- data/test/testing_test.rb +4 -3
- data/test/web_console_test.rb +1 -2
- metadata +16 -4
data/lib/dynflow/future.rb
CHANGED
@@ -1,15 +1,45 @@
|
|
1
1
|
module Dynflow
|
2
2
|
module Listeners
|
3
3
|
module Serialization
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
Job = Algebrick.type do
|
7
|
+
Event = type do
|
8
|
+
fields! execution_plan_id: String,
|
9
|
+
step_id: Fixnum,
|
10
|
+
event: Object
|
11
|
+
end
|
12
|
+
|
13
|
+
Execution = type do
|
14
|
+
fields! execution_plan_id: String
|
15
|
+
end
|
16
|
+
|
17
|
+
variants Event, Execution
|
9
18
|
end
|
10
|
-
Done = type { fields request_id: Integer, execution_plan_uuid: String }
|
11
19
|
|
12
|
-
|
20
|
+
Message = Algebrick.type do
|
21
|
+
Request = type do
|
22
|
+
variants Do = type { fields request_id: Integer, job: Job }
|
23
|
+
end
|
24
|
+
|
25
|
+
Response = type do
|
26
|
+
variants Accepted = type { fields request_id: Integer },
|
27
|
+
Failed = type { fields request_id: Integer, error: String },
|
28
|
+
Done = type { fields request_id: Integer }
|
29
|
+
end
|
30
|
+
|
31
|
+
variants Request, Response
|
32
|
+
end
|
33
|
+
|
34
|
+
module Event
|
35
|
+
def to_hash
|
36
|
+
super.update event: Marshal.dump(event)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.product_from_hash(hash)
|
40
|
+
super(hash.merge 'event' => Marshal.load(hash.fetch('event')))
|
41
|
+
end
|
42
|
+
end
|
13
43
|
end
|
14
44
|
|
15
45
|
def dump(obj)
|
@@ -17,12 +47,16 @@ module Dynflow
|
|
17
47
|
end
|
18
48
|
|
19
49
|
def load(str)
|
20
|
-
|
50
|
+
Protocol::Message.from_hash MultiJson.load(str)
|
21
51
|
end
|
22
52
|
|
23
53
|
def send_message(io, message, barrier = nil)
|
24
54
|
barrier.lock if barrier
|
25
55
|
io.puts dump(message)
|
56
|
+
true
|
57
|
+
rescue SystemCallError => error
|
58
|
+
@logger.warn "message could not be sent #{message} because #{error}"
|
59
|
+
false
|
26
60
|
ensure
|
27
61
|
barrier.unlock if barrier
|
28
62
|
end
|
@@ -5,7 +5,9 @@ module Dynflow
|
|
5
5
|
include Listeners::Serialization
|
6
6
|
include Algebrick::Matching
|
7
7
|
|
8
|
-
|
8
|
+
Terminate = Algebrick.atom
|
9
|
+
|
10
|
+
def initialize(world, socket_path, interval = 1)
|
9
11
|
super(world)
|
10
12
|
|
11
13
|
File.delete socket_path if File.exist? socket_path
|
@@ -14,23 +16,35 @@ module Dynflow
|
|
14
16
|
|
15
17
|
@clients = []
|
16
18
|
@client_barriers = {}
|
17
|
-
@
|
19
|
+
@terminate = false
|
20
|
+
@loop = Thread.new do
|
21
|
+
Thread.current.abort_on_exception = true
|
22
|
+
catch(Terminate) { loop { listen(interval) } }
|
23
|
+
@terminate.resolve true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def terminate(future = Future.new)
|
28
|
+
raise 'multiple calls' if @terminate
|
29
|
+
@terminate = future
|
18
30
|
end
|
19
31
|
|
20
32
|
private
|
21
33
|
|
22
|
-
def listen
|
34
|
+
def listen(interval)
|
35
|
+
shutdown if @terminate
|
36
|
+
|
23
37
|
ios = [@server, *@clients]
|
24
|
-
reads, writes, errors = IO.select(ios, [], ios)
|
25
|
-
reads.each do |readable|
|
38
|
+
reads, writes, errors = IO.select(ios, [], ios, interval)
|
39
|
+
Array(reads).each do |readable|
|
26
40
|
if readable == @server
|
27
41
|
add_client @server.accept
|
28
42
|
logger.info 'Client connected.'
|
29
43
|
|
30
44
|
else
|
31
45
|
match message = receive_message(readable),
|
32
|
-
(on ~
|
33
|
-
|
46
|
+
(on ~Protocol::Do do |(id, job)|
|
47
|
+
execute_job(readable, id, job)
|
34
48
|
end),
|
35
49
|
(on NilClass.to_m do
|
36
50
|
remove_client readable
|
@@ -42,29 +56,36 @@ module Dynflow
|
|
42
56
|
logger.fatal error
|
43
57
|
end
|
44
58
|
|
45
|
-
def
|
59
|
+
def execute_job(readable, id, job)
|
46
60
|
responded = false
|
47
61
|
respond = -> error = nil do
|
48
62
|
unless responded
|
49
63
|
responded = true
|
50
64
|
send_message_to_client(readable, if error
|
51
65
|
logger.error error
|
52
|
-
Failed[id, error.message]
|
66
|
+
Protocol::Failed[id, error.message]
|
53
67
|
else
|
54
|
-
Accepted[id]
|
68
|
+
Protocol::Accepted[id]
|
55
69
|
end)
|
56
70
|
end
|
57
71
|
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
73
|
+
future = Future.new.do_then do |_|
|
74
|
+
if future.resolved?
|
75
|
+
respond.call
|
76
|
+
send_message_to_client readable, Protocol::Done[id]
|
77
|
+
else
|
78
|
+
respond.call future.value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
match job,
|
83
|
+
(on ~Protocol::Execution do |(uuid)|
|
84
|
+
@world.execute(uuid, future)
|
85
|
+
end),
|
86
|
+
(on ~Protocol::Event do |(uuid, step_id, event)|
|
87
|
+
@world.event(uuid, step_id, event, future)
|
88
|
+
end)
|
68
89
|
respond.call
|
69
90
|
rescue Dynflow::Error => e
|
70
91
|
respond.call e
|
@@ -83,6 +104,15 @@ module Dynflow
|
|
83
104
|
def send_message_to_client(client, message)
|
84
105
|
send_message client, message, @client_barriers[client]
|
85
106
|
end
|
107
|
+
|
108
|
+
def shutdown
|
109
|
+
@clients.each { |c| c.shutdown :RDWR }
|
110
|
+
@server.close
|
111
|
+
rescue => e
|
112
|
+
@logger.error e
|
113
|
+
ensure
|
114
|
+
throw Terminate
|
115
|
+
end
|
86
116
|
end
|
87
117
|
end
|
88
118
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class Middleware
|
3
|
+
require 'dynflow/middleware/register'
|
4
|
+
require 'dynflow/middleware/world'
|
5
|
+
require 'dynflow/middleware/action'
|
6
|
+
require 'dynflow/middleware/resolver'
|
7
|
+
require 'dynflow/middleware/stack'
|
8
|
+
|
9
|
+
include Algebrick::TypeCheck
|
10
|
+
|
11
|
+
def initialize(stack)
|
12
|
+
@stack = Type! stack, Stack
|
13
|
+
end
|
14
|
+
|
15
|
+
# call `pass` to get deeper with the call
|
16
|
+
def pass(*args)
|
17
|
+
@stack.pass(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
# to get the action object
|
21
|
+
def action
|
22
|
+
@stack.action or raise "the action is not available"
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(*args)
|
26
|
+
pass(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def plan(*args)
|
30
|
+
pass(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def finalize(*args)
|
34
|
+
pass(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def plan_phase(*args)
|
38
|
+
pass(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def finalize_phase(*args)
|
42
|
+
pass(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class Middleware::Register
|
3
|
+
include Algebrick::TypeCheck
|
4
|
+
|
5
|
+
attr_reader :rules
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@rules = Hash.new do |h, k|
|
9
|
+
h[k] = { before: [],
|
10
|
+
after: [],
|
11
|
+
replace: [] }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def use(middleware_class, options = {})
|
16
|
+
unknown_options = options.keys - [:before, :after, :replace]
|
17
|
+
if unknown_options.any?
|
18
|
+
raise ArgumentError, "Unexpected options: #{unknown_options}"
|
19
|
+
end
|
20
|
+
@rules[middleware_class].merge!(options) do |key, old, new|
|
21
|
+
old + Array(new)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def merge!(register)
|
26
|
+
Type! register, Middleware::Register
|
27
|
+
register.rules.each do |klass, rules|
|
28
|
+
use(klass, rules)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'tsort'
|
2
|
+
|
3
|
+
module Dynflow
|
4
|
+
class Middleware::Resolver
|
5
|
+
|
6
|
+
include TSort
|
7
|
+
include Algebrick::TypeCheck
|
8
|
+
|
9
|
+
def initialize(register)
|
10
|
+
@register = Type! register, Middleware::Register
|
11
|
+
end
|
12
|
+
|
13
|
+
def result
|
14
|
+
@result ||= begin
|
15
|
+
@deps = normalize_rules(@register.rules)
|
16
|
+
self.tsort
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Takes eliminate :replace and :before rules.
|
23
|
+
# Returns hash, that maps middleware classes to their dependencies
|
24
|
+
def normalize_rules(rules)
|
25
|
+
deps = Hash.new { |h, k| h[k] = [] }
|
26
|
+
substitutions = {}
|
27
|
+
|
28
|
+
# replace before with after on oposite direction and build the
|
29
|
+
# substitutions dictionary
|
30
|
+
rules.each do |middleware_class, middleware_rules|
|
31
|
+
deps[middleware_class].concat(middleware_rules[:after])
|
32
|
+
middleware_rules[:before].each do |dependent_class|
|
33
|
+
deps[dependent_class] << middleware_class
|
34
|
+
end
|
35
|
+
middleware_rules[:replace].each do |replaced|
|
36
|
+
substitutions[replaced] = middleware_class
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# replace the middleware to be substituted
|
41
|
+
substitutions.each do |old, new|
|
42
|
+
deps[new].concat(deps[old])
|
43
|
+
deps.delete(old)
|
44
|
+
end
|
45
|
+
|
46
|
+
# ignore deps, that are not present in the stack
|
47
|
+
deps.each do |middleware_class, middleware_deps|
|
48
|
+
middleware_deps.reject! { |dep| !deps.has_key?(dep) }
|
49
|
+
end
|
50
|
+
|
51
|
+
return deps
|
52
|
+
end
|
53
|
+
|
54
|
+
def tsort_each_node(&block)
|
55
|
+
@deps.each_key(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def tsort_each_child(node, &block)
|
59
|
+
@deps.fetch(node).each(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class Middleware::Stack
|
3
|
+
include Algebrick::TypeCheck
|
4
|
+
|
5
|
+
attr_reader :action, :middleware_class, :middleware
|
6
|
+
|
7
|
+
def self.build(middleware_classes, method, action, &block)
|
8
|
+
middleware_classes.reverse_each.reduce(block) do |stack, klass|
|
9
|
+
Middleware::Stack.new(stack, klass, method, action)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(next_stack, middleware_class, method, action)
|
14
|
+
@middleware_class = Child! middleware_class, Middleware
|
15
|
+
@middleware = middleware_class.new self
|
16
|
+
@action = Type! action, Dynflow::Action, NilClass
|
17
|
+
@method = Match! method, :plan, :run, :finalize, :plan_phase, :finalize_phase
|
18
|
+
@next_stack = Type! next_stack, Middleware::Stack, Proc
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(*args)
|
22
|
+
@middleware.send @method, *args
|
23
|
+
end
|
24
|
+
|
25
|
+
def pass(*args)
|
26
|
+
@next_stack.call(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class Middleware::World
|
3
|
+
|
4
|
+
include Algebrick::TypeCheck
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@register = Middleware::Register.new
|
8
|
+
clear_cache!
|
9
|
+
end
|
10
|
+
|
11
|
+
def use(*args)
|
12
|
+
clear_cache!
|
13
|
+
@register.use(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(method, action_or_class, *args, &block)
|
17
|
+
Match! method, :plan, :run, :finalize, :plan_phase, :finalize_phase
|
18
|
+
if Child? action_or_class, Dynflow::Action
|
19
|
+
action = nil
|
20
|
+
action_class = action_or_class
|
21
|
+
elsif Type? action_or_class, Dynflow::Action
|
22
|
+
action = action_or_class
|
23
|
+
action_class = action.action_class
|
24
|
+
else
|
25
|
+
Algebrick::TypeCheck.error action_or_class, 'is not instance or child class', Dynflow::Action
|
26
|
+
end
|
27
|
+
|
28
|
+
classes = middleware_classes(action_class)
|
29
|
+
stack = Middleware::Stack.build(classes, method, action, &block)
|
30
|
+
stack.call(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_cache!
|
34
|
+
@middleware_classes_cache = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def cumulate_register(action_class, register = Middleware::Register.new)
|
40
|
+
register.merge!(@register)
|
41
|
+
unless action_class == Dynflow::Action
|
42
|
+
cumulate_register(action_class.superclass, register)
|
43
|
+
end
|
44
|
+
register.merge!(action_class.middleware)
|
45
|
+
return register
|
46
|
+
end
|
47
|
+
|
48
|
+
def middleware_classes(action_class)
|
49
|
+
unless @middleware_classes_cache.key?(action_class)
|
50
|
+
register = cumulate_register(action_class)
|
51
|
+
resolver = Dynflow::Middleware::Resolver.new(register)
|
52
|
+
@middleware_classes_cache[action_class] = resolver.result
|
53
|
+
end
|
54
|
+
return @middleware_classes_cache[action_class]
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|