dynflow 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/lib/dynflow.rb +2 -1
  2. data/lib/dynflow/action.rb +271 -101
  3. data/lib/dynflow/action/format.rb +4 -4
  4. data/lib/dynflow/action/progress.rb +8 -8
  5. data/lib/dynflow/execution_plan.rb +14 -15
  6. data/lib/dynflow/execution_plan/output_reference.rb +56 -23
  7. data/lib/dynflow/execution_plan/steps/abstract.rb +1 -3
  8. data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +0 -17
  9. data/lib/dynflow/execution_plan/steps/error.rb +35 -11
  10. data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -1
  11. data/lib/dynflow/execution_plan/steps/plan_step.rb +8 -3
  12. data/lib/dynflow/execution_plan/steps/run_step.rb +1 -1
  13. data/lib/dynflow/listeners/socket.rb +0 -1
  14. data/lib/dynflow/middleware.rb +0 -1
  15. data/lib/dynflow/middleware/world.rb +1 -1
  16. data/lib/dynflow/persistence.rb +2 -5
  17. data/lib/dynflow/serializable.rb +24 -11
  18. data/lib/dynflow/testing/assertions.rb +5 -5
  19. data/lib/dynflow/testing/dummy_execution_plan.rb +1 -1
  20. data/lib/dynflow/testing/dummy_planned_action.rb +2 -1
  21. data/lib/dynflow/testing/dummy_world.rb +4 -0
  22. data/lib/dynflow/testing/factories.rb +18 -10
  23. data/lib/dynflow/version.rb +1 -1
  24. data/lib/dynflow/world.rb +35 -21
  25. data/test/action_test.rb +50 -76
  26. data/test/clock_test.rb +4 -4
  27. data/test/execution_plan_test.rb +1 -1
  28. data/test/executor_test.rb +3 -2
  29. data/test/remote_via_socket_test.rb +11 -9
  30. data/test/support/code_workflow_example.rb +2 -3
  31. data/test/test_helper.rb +1 -1
  32. data/test/testing_test.rb +3 -3
  33. metadata +2 -8
  34. data/lib/dynflow/action/finalize_phase.rb +0 -20
  35. data/lib/dynflow/action/flow_phase.rb +0 -44
  36. data/lib/dynflow/action/plan_phase.rb +0 -87
  37. data/lib/dynflow/action/presenter.rb +0 -51
  38. data/lib/dynflow/action/run_phase.rb +0 -45
  39. data/lib/dynflow/middleware/action.rb +0 -9
@@ -14,12 +14,12 @@ module Dynflow
14
14
  when !block && @input_format_block
15
15
  return @input_format ||= Apipie::Params::Description.define(&@input_format_block)
16
16
  when block && @input_format_block
17
- raise "The input_format has already been defined in #{self.action_class}"
17
+ raise "The input_format has already been defined in #{self.class}"
18
18
  when !block && !@input_format_block
19
19
  if superclass.respond_to? :input_format
20
20
  superclass.input_format
21
21
  else
22
- raise "The input_format has not been defined yet in #{self.action_class}"
22
+ raise "The input_format has not been defined yet in #{self.class}"
23
23
  end
24
24
  end
25
25
  end
@@ -31,12 +31,12 @@ module Dynflow
31
31
  when !block && @output_format_block
32
32
  return @output_format ||= Apipie::Params::Description.define(&@output_format_block)
33
33
  when block && @output_format_block
34
- raise "The output_format has already been defined in #{self.action_class}"
34
+ raise "The output_format has already been defined in #{self.class}"
35
35
  when !block && !@output_format_block
36
36
  if superclass.respond_to? :output_format
37
37
  superclass.output_format
38
38
  else
39
- raise "The output_format has not been defined yet in #{self.action_class}"
39
+ raise "The output_format has not been defined yet in #{self.class}"
40
40
  end
41
41
  end
42
42
  end
@@ -33,13 +33,13 @@ module Dynflow
33
33
  when :success, :skipped
34
34
  1
35
35
  when :running, :suspended
36
- case self
37
- when Action::RunPhase
36
+ case phase
37
+ when Action::Run
38
38
  run_progress
39
- when Action::FinalizePhase
39
+ when Action::Finalize
40
40
  finalize_progress
41
41
  else
42
- raise "Calculating progress for this phase is not supported"
42
+ raise 'Calculating progress for this phase is not supported'
43
43
  end
44
44
  else
45
45
  0
@@ -47,13 +47,13 @@ module Dynflow
47
47
  end
48
48
 
49
49
  def progress_weight
50
- case self
51
- when Action::RunPhase
50
+ case phase
51
+ when Action::Run
52
52
  run_progress_weight
53
- when Action::FinalizePhase
53
+ when Action::Finalize
54
54
  finalize_progress_weight
55
55
  else
56
- raise "Calculating progress for this phase is not supported"
56
+ raise 'Calculating progress for this phase is not supported'
57
57
  end
58
58
  end
59
59
 
@@ -96,6 +96,10 @@ module Dynflow
96
96
  result == :error
97
97
  end
98
98
 
99
+ def errors
100
+ steps.values.map(&:error).compact
101
+ end
102
+
99
103
  def generate_action_id
100
104
  @last_action_id ||= 0
101
105
  @last_action_id += 1
@@ -149,7 +153,7 @@ module Dynflow
149
153
  # FIND maybe move to persistence to let adapter to do it effectively?
150
154
  # @return [Array<Steps::Abstract>]
151
155
  def steps_to_skip(step)
152
- dependent_steps = @steps.values.find_all do |s|
156
+ dependent_steps = steps.values.find_all do |s|
153
157
  next if s.is_a? Steps::PlanStep
154
158
  action = persistence.load_action(s)
155
159
  action.required_step_ids.include?(step.id)
@@ -199,14 +203,14 @@ module Dynflow
199
203
  end
200
204
 
201
205
  def add_run_step(action)
202
- add_step(Steps::RunStep, action.action_class, action.id).tap do |step|
206
+ add_step(Steps::RunStep, action.class, action.id).tap do |step|
203
207
  @dependency_graph.add_dependencies(step, action)
204
208
  current_run_flow.add_and_resolve(@dependency_graph, Flows::Atom.new(step.id))
205
209
  end
206
210
  end
207
211
 
208
212
  def add_finalize_step(action)
209
- add_step(Steps::FinalizeStep, action.action_class, action.id).tap do |step|
213
+ add_step(Steps::FinalizeStep, action.class, action.id).tap do |step|
210
214
  finalize_flow << Flows::Atom.new(step.id)
211
215
  end
212
216
  end
@@ -264,19 +268,14 @@ module Dynflow
264
268
  # @return [Array<Action::Presenter>] presenter of the actions
265
269
  # involved in the plan
266
270
  def actions
267
- action_steps = Hash.new { |h, k| h[k] = [] }
268
- all_actions = []
269
- steps.values.each do |step|
270
- action_steps[step.action_id] << step
271
- end
272
- action_steps.each do |action_id, involved_steps|
273
- action = Action::Presenter.load(self,
274
- action_id,
275
- involved_steps,
276
- all_actions)
277
- all_actions << action
271
+ @actions ||= begin
272
+ action_ids = steps.values.map(&:action_id).uniq
273
+ action_ids.map do |action_id|
274
+ attributes = world.persistence.adapter.load_action(id, action_id)
275
+ Action.from_hash(attributes.update(phase: Action::Present, execution_plan: self),
276
+ world)
277
+ end
278
278
  end
279
- return all_actions
280
279
  end
281
280
 
282
281
  private
@@ -1,51 +1,84 @@
1
1
  module Dynflow
2
2
  class ExecutionPlan::OutputReference < Serializable
3
+ include Algebrick::TypeCheck
3
4
 
4
- attr_reader :step_id, :action_id, :subkeys
5
+ # dereferences all OutputReferences in Hash-Array structure
6
+ def self.dereference(object, persistence)
7
+ case object
8
+ when Hash
9
+ object.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
10
+ h.update(key => dereference(val, persistence))
11
+ end
12
+ when Array
13
+ object.map { |val| dereference(val, persistence) }
14
+ when self
15
+ object.dereference(persistence)
16
+ else
17
+ object
18
+ end
19
+ end
20
+
21
+ # dereferences all hashes representing OutputReferences in Hash-Array structure
22
+ def self.deserialize(value)
23
+ case value
24
+ when Hash
25
+ if value[:class] == self.to_s
26
+ new_from_hash(value)
27
+ else
28
+ value.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
29
+ h.update(key => deserialize(val))
30
+ end
31
+ end
32
+ when Array
33
+ value.map { |val| deserialize(val) }
34
+ else
35
+ value
36
+ end
37
+ end
38
+
39
+ attr_reader :execution_plan_id, :step_id, :action_id, :subkeys
5
40
 
6
- def initialize(step_id, action_id, subkeys = [])
7
- @step_id = step_id
8
- @action_id = action_id
9
- @subkeys = subkeys
41
+ def initialize(execution_plan_id, step_id, action_id, subkeys = [])
42
+ @execution_plan_id = Type! execution_plan_id, String
43
+ @step_id = Type! step_id, Integer
44
+ @action_id = Type! action_id, Integer
45
+ Type! subkeys, Array
46
+ @subkeys = subkeys.map { |v| Type!(v, String, Symbol).to_s }.freeze
10
47
  end
11
48
 
12
49
  def [](subkey)
13
- return self.class.new(step_id, action_id, subkeys.dup << subkey)
50
+ self.class.new(execution_plan_id, step_id, action_id, subkeys + [subkey])
14
51
  end
15
52
 
16
53
  def to_hash
17
- recursive_to_hash class: self.class.to_s,
18
- step_id: step_id,
19
- action_id: action_id,
20
- subkeys: subkeys
54
+ recursive_to_hash class: self.class.to_s,
55
+ execution_plan_id: execution_plan_id,
56
+ step_id: step_id,
57
+ action_id: action_id,
58
+ subkeys: subkeys
21
59
  end
22
60
 
23
61
  def to_s
24
- "Step(#{@step_id}).output".tap do |ret|
25
- ret << @subkeys.map { |k| "[:#{k}]" }.join('') if @subkeys.any?
62
+ "Step(#{step_id}).output".tap do |ret|
63
+ ret << subkeys.map { |k| "[:#{k}]" }.join('') if subkeys.any?
26
64
  end
27
65
  end
28
66
 
29
67
  alias_method :inspect, :to_s
30
68
 
31
- def dereference(persistence, execution_plan_id)
69
+ def dereference(persistence)
32
70
  action_data = persistence.adapter.load_action(execution_plan_id, action_id)
33
- deref = action_data[:output]
34
- @subkeys.each do |subkey|
35
- if deref.respond_to?(:[])
36
- deref = deref[subkey]
37
- else
38
- raise "We were not able to dereference subkey #{@subkeys} from #{self.inspect}"
39
- end
40
- end
41
- return deref
71
+ @subkeys.reduce(action_data[:output]) { |v, k| v.fetch k }
42
72
  end
43
73
 
44
74
  protected
45
75
 
46
76
  def self.new_from_hash(hash)
47
77
  check_class_matching hash
48
- new(hash[:step_id], hash[:action_id], hash[:subkeys])
78
+ new(hash.fetch(:execution_plan_id),
79
+ hash.fetch(:step_id),
80
+ hash.fetch(:action_id),
81
+ hash.fetch(:subkeys))
49
82
  end
50
83
 
51
84
  end
@@ -31,9 +31,7 @@ module Dynflow
31
31
 
32
32
  self.state = state.to_sym
33
33
 
34
- Type! action_class, Class
35
- raise ArgumentError, 'action_class is not an child of Action' unless action_class < Action
36
- raise ArgumentError, 'action_class must not be phase' if action_class.phase?
34
+ Child! action_class, Action
37
35
  @action_class = action_class
38
36
 
39
37
  @action_id = action_id || raise(ArgumentError, 'missing action_id')
@@ -5,7 +5,6 @@ module Dynflow
5
5
  def execute(*args)
6
6
  return self if [:skipped, :success].include? self.state
7
7
  open_action do |action|
8
- action.indifferent_access_hash_variable_set :input, dereference(action.input)
9
8
  with_time_calculation do
10
9
  action.execute(*args)
11
10
  end
@@ -16,7 +15,6 @@ module Dynflow
16
15
  self.class.from_hash(to_hash, execution_plan_id, world)
17
16
  end
18
17
 
19
-
20
18
  def progress
21
19
  action = persistence.load_action(self)
22
20
  [action.progress_done, action.progress_weight]
@@ -32,21 +30,6 @@ module Dynflow
32
30
 
33
31
  return self
34
32
  end
35
-
36
- def dereference(input)
37
- case input
38
- when Hash
39
- input.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
40
- h.update(key => dereference(val))
41
- end
42
- when Array
43
- input.map { |val| dereference(val) }
44
- when ExecutionPlan::OutputReference
45
- input.dereference(persistence, execution_plan_id)
46
- else
47
- input
48
- end
49
- end
50
33
  end
51
34
  end
52
35
  end
@@ -1,32 +1,56 @@
1
1
  module Dynflow
2
2
  module ExecutionPlan::Steps
3
3
  class Error < Serializable
4
+ extend Algebrick::Matching
5
+ include Algebrick::TypeCheck
4
6
 
5
7
  attr_reader :exception_class, :message, :backtrace
6
8
 
7
- def initialize(exception_class, message, backtrace)
8
- @exception_class = exception_class
9
- @message = message
10
- @backtrace = backtrace
9
+ def self.new(*args)
10
+ case args.size
11
+ when 1
12
+ match obj = args.first,
13
+ (on String do
14
+ super(StandardError, obj, caller, nil)
15
+ end),
16
+ (on Exception do
17
+ super(obj.class, obj.message, obj.backtrace, obj)
18
+ end)
19
+ when 3, 4
20
+ super(*args.values_at(0..3))
21
+ else
22
+ raise ArgumentError, "wrong number of arguments #{args}"
23
+ end
24
+ end
25
+
26
+ def initialize(exception_class, message, backtrace, exception)
27
+ @exception_class = Child! exception_class, Exception
28
+ @message = Type! message, String
29
+ @backtrace = Type! backtrace, Array
30
+ @exception = Type! exception, Exception, NilClass
11
31
  end
12
32
 
13
33
  def self.new_from_hash(hash)
14
- self.new(hash[:exception_class], hash[:message], hash[:backtrace])
34
+ self.new(hash[:exception_class].constantize, hash[:message], hash[:backtrace], nil)
15
35
  end
16
36
 
17
37
  def to_hash
18
- { class: self.class.name,
19
- exception_class: exception_class,
20
- message: message,
21
- backtrace: backtrace }
38
+ recursive_to_hash class: self.class.name,
39
+ exception_class: exception_class.to_s,
40
+ message: message,
41
+ backtrace: backtrace
22
42
  end
23
43
 
24
44
  def to_s
25
- "#{message} (#{exception_class})\n#{(backtrace || []).join("\n")}"
45
+ format '%s (%s)\n%s',
46
+ (@exception || self).message,
47
+ (@exception ? @exception.class : exception_class),
48
+ (@exception || self).backtrace
26
49
  end
27
50
 
28
51
  def exception
29
- exception_class.constantize.exception(message).tap { |e| e.set_backtrace backtrace }
52
+ @exception ||
53
+ exception_class.exception(message).tap { |e| e.set_backtrace backtrace }
30
54
  end
31
55
  end
32
56
  end
@@ -15,7 +15,7 @@ module Dynflow
15
15
 
16
16
 
17
17
  def phase
18
- :finalize_phase
18
+ Action::Finalize
19
19
  end
20
20
 
21
21
  end
@@ -24,7 +24,7 @@ module Dynflow
24
24
  end
25
25
 
26
26
  def phase
27
- :plan_phase
27
+ Action::Plan
28
28
  end
29
29
 
30
30
  def to_hash
@@ -37,8 +37,13 @@ module Dynflow
37
37
  attributes = { execution_plan_id: execution_plan.id,
38
38
  id: action_id,
39
39
  step: self,
40
- plan_step_id: self.id }
41
- action = action_class.plan_phase.new(attributes, execution_plan, trigger)
40
+ plan_step_id: self.id,
41
+ run_step_id: nil,
42
+ finalize_step_id: nil,
43
+ phase: phase,
44
+ execution_plan: execution_plan,
45
+ trigger: trigger }
46
+ action = action_class.new(attributes, execution_plan.world)
42
47
  persistence.save_action(execution_plan_id, action)
43
48
 
44
49
  with_time_calculation do
@@ -14,7 +14,7 @@ module Dynflow
14
14
  end
15
15
 
16
16
  def phase
17
- :run_phase
17
+ Action::Run
18
18
  end
19
19
  end
20
20
  end
@@ -1,7 +1,6 @@
1
1
  module Dynflow
2
2
  module Listeners
3
3
  class Socket < Abstract
4
- # TODO terminate when exiting
5
4
  include Listeners::Serialization
6
5
  include Algebrick::Matching
7
6
 
@@ -2,7 +2,6 @@ module Dynflow
2
2
  class Middleware
3
3
  require 'dynflow/middleware/register'
4
4
  require 'dynflow/middleware/world'
5
- require 'dynflow/middleware/action'
6
5
  require 'dynflow/middleware/resolver'
7
6
  require 'dynflow/middleware/stack'
8
7
 
@@ -20,7 +20,7 @@ module Dynflow
20
20
  action_class = action_or_class
21
21
  elsif Type? action_or_class, Dynflow::Action
22
22
  action = action_or_class
23
- action_class = action.action_class
23
+ action_class = action.class
24
24
  else
25
25
  Algebrick::TypeCheck.error action_or_class, 'is not instance or child class', Dynflow::Action
26
26
  end
@@ -14,11 +14,8 @@ module Dynflow
14
14
  def load_action(step)
15
15
  attributes = adapter.
16
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)
17
+ update(step: step, phase: step.phase)
18
+ return Action.from_hash(attributes, step.world)
22
19
  end
23
20
 
24
21
  def save_action(execution_plan_id, action)
@@ -2,7 +2,7 @@ module Dynflow
2
2
  class Serializable
3
3
  def self.from_hash(hash, *args)
4
4
  check_class_key_present hash
5
- hash[:class].constantize.new_from_hash(hash, *args)
5
+ constantize(hash[:class]).new_from_hash(hash, *args)
6
6
  end
7
7
 
8
8
  def to_hash
@@ -26,26 +26,39 @@ module Dynflow
26
26
  raise ArgumentError, 'missing :class' unless hash[:class]
27
27
  end
28
28
 
29
+ def self.constantize(action_name)
30
+ action_name.constantize
31
+ end
32
+
29
33
  private_class_method :check_class_matching, :check_class_key_present
30
34
 
31
35
  private
32
36
 
33
- def recursive_to_hash(value)
34
- case value
35
- when Numeric, String, Symbol, TrueClass, FalseClass, NilClass
36
- value
37
- when Array
38
- value.map { |v| recursive_to_hash v }
39
- when Hash
40
- value.inject({}) { |h, (k, v)| h.update k => recursive_to_hash(v) }
37
+ # recursively traverses hash-array structure and converts all to hashes
38
+ # accepts more hashes which are then merged
39
+ def recursive_to_hash(*values)
40
+ if values.size == 1
41
+ value = values.first
42
+ case value
43
+ when Numeric, String, Symbol, TrueClass, FalseClass, NilClass
44
+ value
45
+ when Array
46
+ value.map { |v| recursive_to_hash v }
47
+ when Hash
48
+ value.inject({}) { |h, (k, v)| h.update k => recursive_to_hash(v) }
49
+ else
50
+ value.to_hash
51
+ end
41
52
  else
42
- value.to_hash
53
+ values.all? { |v| Type! v, Hash, NilClass }
54
+ recursive_to_hash(values.compact.reduce { |h, v| h.merge v })
43
55
  end
44
56
  end
45
57
 
46
58
  def self.string_to_time(string)
47
59
  return if string.nil?
48
- _, year, month, day, hour, min, sec = */(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/.match(string)
60
+ _, year, month, day, hour, min, sec =
61
+ */(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/.match(string)
49
62
  Time.new(year.to_i, month.to_i, day.to_i, hour.to_i, min.to_i, sec.to_i)
50
63
  end
51
64