dynflow 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.travis.yml +1 -0
  2. data/lib/dynflow.rb +1 -0
  3. data/lib/dynflow/action.rb +5 -3
  4. data/lib/dynflow/action/finalize_phase.rb +3 -1
  5. data/lib/dynflow/action/plan_phase.rb +4 -2
  6. data/lib/dynflow/action/run_phase.rb +3 -1
  7. data/lib/dynflow/daemon.rb +1 -0
  8. data/lib/dynflow/execution_plan.rb +6 -4
  9. data/lib/dynflow/executors/abstract.rb +12 -0
  10. data/lib/dynflow/executors/parallel.rb +16 -35
  11. data/lib/dynflow/executors/parallel/core.rb +13 -8
  12. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +21 -29
  13. data/lib/dynflow/executors/parallel/flow_manager.rb +0 -3
  14. data/lib/dynflow/executors/parallel/running_steps_manager.rb +5 -3
  15. data/lib/dynflow/executors/parallel/sequential_manager.rb +6 -2
  16. data/lib/dynflow/executors/parallel/worker.rb +5 -4
  17. data/lib/dynflow/executors/remote_via_socket.rb +7 -2
  18. data/lib/dynflow/executors/remote_via_socket/core.rb +66 -32
  19. data/lib/dynflow/future.rb +1 -1
  20. data/lib/dynflow/listeners/abstract.rb +4 -0
  21. data/lib/dynflow/listeners/serialization.rb +42 -8
  22. data/lib/dynflow/listeners/socket.rb +49 -19
  23. data/lib/dynflow/middleware.rb +46 -0
  24. data/lib/dynflow/middleware/action.rb +9 -0
  25. data/lib/dynflow/middleware/register.rb +32 -0
  26. data/lib/dynflow/middleware/resolver.rb +63 -0
  27. data/lib/dynflow/middleware/stack.rb +29 -0
  28. data/lib/dynflow/middleware/world.rb +58 -0
  29. data/lib/dynflow/simple_world.rb +1 -0
  30. data/lib/dynflow/testing/dummy_world.rb +3 -1
  31. data/lib/dynflow/version.rb +1 -1
  32. data/lib/dynflow/web_console.rb +7 -2
  33. data/lib/dynflow/world.rb +29 -9
  34. data/test/action_test.rb +5 -6
  35. data/test/execution_plan_test.rb +10 -11
  36. data/test/executor_test.rb +152 -89
  37. data/test/middleware_test.rb +109 -0
  38. data/test/remote_via_socket_test.rb +166 -0
  39. data/test/{code_workflow_example.rb → support/code_workflow_example.rb} +39 -30
  40. data/test/support/middleware_example.rb +132 -0
  41. data/test/test_helper.rb +18 -16
  42. data/test/testing_test.rb +4 -3
  43. data/test/web_console_test.rb +1 -2
  44. metadata +16 -4
@@ -1,6 +1,6 @@
1
1
  module Dynflow
2
2
  class Future
3
- Error = Class.new StandardError
3
+ Error = Class.new Dynflow::Error
4
4
  FutureAlreadySet = Class.new Error
5
5
  FutureFailed = Class.new Error
6
6
  TimeOut = Class.new Error
@@ -8,6 +8,10 @@ module Dynflow
8
8
  @world = Type! world, World
9
9
  @logger = world.logger
10
10
  end
11
+
12
+ def terminate(future = Future.new)
13
+ raise NotImplementedError
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -1,15 +1,45 @@
1
1
  module Dynflow
2
2
  module Listeners
3
3
  module Serialization
4
- SocketMessage = Algebrick.type do
5
- Execute = type { fields request_id: Integer, execution_plan_uuid: String }
6
- Confirmation = type do
7
- variants Accepted = type { fields request_id: Integer },
8
- Failed = type { fields request_id: Integer, error: String }
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
- variants Execute, Confirmation, Done
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
- SocketMessage.from_hash MultiJson.load(str)
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
- def initialize(world, socket_path)
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
- @loop = Thread.new { loop { listen } }
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 ~Execute do |(id, uuid)|
33
- execute(readable, id, uuid)
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 execute(readable, id, uuid)
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
- @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)
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,9 @@
1
+ module Dynflow
2
+ module Middleware::Action
3
+
4
+ def middleware
5
+ @middleware ||= Middleware::Register.new
6
+ end
7
+
8
+ end
9
+ 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