dynflow 0.3.0 → 0.4.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 (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