dynflow 0.8.3 → 0.8.4

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 (44) hide show
  1. checksums.yaml +8 -8
  2. data/doc/pages/source/documentation/index.md +14 -14
  3. data/dynflow.gemspec +0 -1
  4. data/examples/future_execution.rb +7 -7
  5. data/lib/dynflow.rb +3 -3
  6. data/lib/dynflow/action.rb +7 -9
  7. data/lib/dynflow/action/with_sub_plans.rb +9 -3
  8. data/lib/dynflow/config.rb +2 -2
  9. data/lib/dynflow/coordinator.rb +3 -3
  10. data/lib/dynflow/delayed_executors.rb +9 -0
  11. data/lib/dynflow/{schedulers → delayed_executors}/abstract.rb +3 -3
  12. data/lib/dynflow/{schedulers → delayed_executors}/abstract_core.rb +7 -7
  13. data/lib/dynflow/{schedulers → delayed_executors}/polling.rb +6 -6
  14. data/lib/dynflow/{scheduled_plan.rb → delayed_plan.rb} +4 -4
  15. data/lib/dynflow/execution_plan.rb +10 -10
  16. data/lib/dynflow/execution_plan/output_reference.rb +2 -2
  17. data/lib/dynflow/execution_plan/steps/error.rb +1 -1
  18. data/lib/dynflow/execution_plan/steps/plan_step.rb +2 -2
  19. data/lib/dynflow/middleware.rb +1 -1
  20. data/lib/dynflow/middleware/stack.rb +1 -1
  21. data/lib/dynflow/middleware/world.rb +1 -1
  22. data/lib/dynflow/persistence.rb +10 -10
  23. data/lib/dynflow/persistence_adapters/abstract.rb +4 -4
  24. data/lib/dynflow/persistence_adapters/sequel.rb +17 -17
  25. data/lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb +5 -0
  26. data/lib/dynflow/serializable.rb +1 -1
  27. data/lib/dynflow/serializer.rb +1 -1
  28. data/lib/dynflow/testing/assertions.rb +1 -1
  29. data/lib/dynflow/utils.rb +205 -0
  30. data/lib/dynflow/version.rb +1 -1
  31. data/lib/dynflow/web/console_helpers.rb +1 -1
  32. data/lib/dynflow/web/filtering_helpers.rb +3 -3
  33. data/lib/dynflow/world.rb +16 -16
  34. data/test/action_test.rb +4 -2
  35. data/test/future_execution_test.rb +32 -32
  36. data/test/middleware_test.rb +5 -5
  37. data/test/persistence_test.rb +3 -3
  38. data/test/support/dummy_example.rb +2 -2
  39. data/test/support/middleware_example.rb +5 -5
  40. data/test/test_helper.rb +1 -1
  41. data/test/testing_test.rb +1 -1
  42. data/web/views/show.erb +3 -3
  43. metadata +9 -21
  44. data/lib/dynflow/schedulers.rb +0 -9
@@ -6,7 +6,7 @@ module Dynflow
6
6
  def self.dereference(object, persistence)
7
7
  case object
8
8
  when Hash
9
- object.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
9
+ object.reduce(Utils.indifferent_hash({})) do |h, (key, val)|
10
10
  h.update(key => dereference(val, persistence))
11
11
  end
12
12
  when Array
@@ -25,7 +25,7 @@ module Dynflow
25
25
  if value[:class] == self.to_s
26
26
  new_from_hash(value)
27
27
  else
28
- value.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
28
+ value.reduce(Utils.indifferent_hash({})) do |h, (key, val)|
29
29
  h.update(key => deserialize(val))
30
30
  end
31
31
  end
@@ -32,7 +32,7 @@ module Dynflow
32
32
 
33
33
  def self.new_from_hash(hash)
34
34
  exception_class = begin
35
- hash[:exception_class].constantize
35
+ Utils.constantize(hash[:exception_class])
36
36
  rescue NameError
37
37
  Errors::UnknownError.for_exception_class(hash[:exception_class])
38
38
  end
@@ -35,8 +35,8 @@ module Dynflow
35
35
  super.merge recursive_to_hash(:children => children)
36
36
  end
37
37
 
38
- def schedule(schedule_options, args)
39
- @action.execute_schedule(schedule_options, *args)
38
+ def delay(delay_options, args)
39
+ @action.execute_delay(delay_options, *args)
40
40
  @action.serializer
41
41
  ensure
42
42
  save
@@ -21,7 +21,7 @@ module Dynflow
21
21
  @stack.action or raise "the action is not available"
22
22
  end
23
23
 
24
- def schedule(*args)
24
+ def delay(*args)
25
25
  pass(*args)
26
26
  end
27
27
 
@@ -14,7 +14,7 @@ module Dynflow
14
14
  @middleware_class = Child! middleware_class, Middleware
15
15
  @middleware = middleware_class.new self
16
16
  @action = Type! action, Dynflow::Action, NilClass
17
- @method = Match! method, :schedule, :plan, :run, :finalize, :plan_phase, :finalize_phase
17
+ @method = Match! method, :delay, :plan, :run, :finalize, :plan_phase, :finalize_phase
18
18
  @next_stack = Type! next_stack, Middleware::Stack, Proc
19
19
  end
20
20
 
@@ -14,7 +14,7 @@ module Dynflow
14
14
  end
15
15
 
16
16
  def execute(method, action_or_class, *args, &block)
17
- Match! method, :schedule, :plan, :run, :finalize, :plan_phase, :finalize_phase
17
+ Match! method, :delay, :plan, :run, :finalize, :plan_phase, :finalize_phase
18
18
  if Child? action_or_class, Dynflow::Action
19
19
  action = nil
20
20
  action_class = action_or_class
@@ -49,24 +49,24 @@ module Dynflow
49
49
  adapter.save_execution_plan(execution_plan.id, execution_plan.to_hash)
50
50
  end
51
51
 
52
- def find_past_scheduled_plans(time)
53
- adapter.find_past_scheduled_plans(time).map do |plan|
54
- ScheduledPlan.new_from_hash(@world, plan)
52
+ def find_past_delayed_plans(time)
53
+ adapter.find_past_delayed_plans(time).map do |plan|
54
+ DelayedPlan.new_from_hash(@world, plan)
55
55
  end
56
56
  end
57
57
 
58
- def delete_scheduled_plans(filters, batch_size = 1000)
59
- adapter.delete_scheduled_plans(filters, batch_size)
58
+ def delete_delayed_plans(filters, batch_size = 1000)
59
+ adapter.delete_delayed_plans(filters, batch_size)
60
60
  end
61
61
 
62
- def save_scheduled_plan(schedule)
63
- adapter.save_scheduled_plan(schedule.execution_plan_uuid, schedule.to_hash)
62
+ def save_delayed_plan(delayed_plan)
63
+ adapter.save_delayed_plan(delayed_plan.execution_plan_uuid, delayed_plan.to_hash)
64
64
  end
65
65
 
66
- def load_scheduled_plan(execution_plan_id)
67
- hash = adapter.load_scheduled_plan(execution_plan_id)
66
+ def load_delayed_plan(execution_plan_id)
67
+ hash = adapter.load_delayed_plan(execution_plan_id)
68
68
  return nil unless hash
69
- ScheduledPlan.new_from_hash(@world, hash)
69
+ DelayedPlan.new_from_hash(@world, hash)
70
70
  end
71
71
 
72
72
  def load_step(execution_plan_id, step_id, world)
@@ -55,19 +55,19 @@ module Dynflow
55
55
  raise NotImplementedError
56
56
  end
57
57
 
58
- def find_past_scheduled_plans(options = {})
58
+ def find_past_delayed_plans(options = {})
59
59
  raise NotImplementedError
60
60
  end
61
61
 
62
- def delete_scheduled_plans(filters, batch_size = 1000)
62
+ def delete_delayed_plans(filters, batch_size = 1000)
63
63
  raise NotImplementedError
64
64
  end
65
65
 
66
- def load_scheduled_plan(execution_plan_id)
66
+ def load_delayed_plan(execution_plan_id)
67
67
  raise NotImplementedError
68
68
  end
69
69
 
70
- def save_scheduled_plan(execution_plan_id, value)
70
+ def save_delayed_plan(execution_plan_id, value)
71
71
  raise NotImplementedError
72
72
  end
73
73
 
@@ -32,7 +32,7 @@ module Dynflow
32
32
  step: %w(state started_at ended_at real_time execution_time action_id progress_done progress_weight),
33
33
  envelope: %w(receiver_id),
34
34
  coordinator_record: %w(id owner_id class),
35
- scheduled: %w(execution_plan_uuid start_at start_before args_serializer)}
35
+ delayed: %w(execution_plan_uuid start_at start_before args_serializer)}
36
36
 
37
37
  def initialize(config)
38
38
  config = config.dup
@@ -63,7 +63,7 @@ module Dynflow
63
63
  filter(:execution_plan, table(:execution_plan), filters).each_slice(batch_size) do |plans|
64
64
  uuids = plans.map { |p| p.fetch(:uuid) }
65
65
  @db.transaction do
66
- table(:scheduled).where(execution_plan_uuid: uuids).delete
66
+ table(:delayed).where(execution_plan_uuid: uuids).delete
67
67
  table(:step).where(execution_plan_uuid: uuids).delete
68
68
  table(:action).where(execution_plan_uuid: uuids).delete
69
69
  count += table(:execution_plan).where(uuid: uuids).delete
@@ -80,33 +80,33 @@ module Dynflow
80
80
  save :execution_plan, { uuid: execution_plan_id }, value
81
81
  end
82
82
 
83
- def delete_scheduled_plans(filters, batch_size = 1000)
83
+ def delete_delayed_plans(filters, batch_size = 1000)
84
84
  count = 0
85
- filter(:scheduled, table(:scheduled), filters).each_slice(batch_size) do |plans|
85
+ filter(:delayed, table(:delayed), filters).each_slice(batch_size) do |plans|
86
86
  uuids = plans.map { |p| p.fetch(:execution_plan_uuid) }
87
87
  @db.transaction do
88
- count += table(:scheduled).where(execution_plan_uuid: uuids).delete
88
+ count += table(:delayed).where(execution_plan_uuid: uuids).delete
89
89
  end
90
90
  end
91
91
  count
92
92
  end
93
93
 
94
- def find_past_scheduled_plans(time)
95
- table(:scheduled)
94
+ def find_past_delayed_plans(time)
95
+ table(:delayed)
96
96
  .where('start_at <= ?', time)
97
97
  .order_by(:start_at)
98
98
  .all
99
99
  .map { |plan| load_data(plan) }
100
100
  end
101
101
 
102
- def load_scheduled_plan(execution_plan_id)
103
- load :scheduled, execution_plan_uuid: execution_plan_id
102
+ def load_delayed_plan(execution_plan_id)
103
+ load :delayed, execution_plan_uuid: execution_plan_id
104
104
  rescue KeyError
105
105
  return nil
106
106
  end
107
107
 
108
- def save_scheduled_plan(execution_plan_id, value)
109
- save :scheduled, { execution_plan_uuid: execution_plan_id }, value
108
+ def save_delayed_plan(execution_plan_id, value)
109
+ save :delayed, { execution_plan_uuid: execution_plan_id }, value
110
110
  end
111
111
 
112
112
  def load_step(execution_plan_id, step_id)
@@ -195,7 +195,7 @@ module Dynflow
195
195
  step: :dynflow_steps,
196
196
  envelope: :dynflow_envelopes,
197
197
  coordinator_record: :dynflow_coordinator_records,
198
- scheduled: :dynflow_scheduled_plans }
198
+ delayed: :dynflow_delayed_plans }
199
199
 
200
200
  def table(which)
201
201
  db[TABLES.fetch(which)]
@@ -243,7 +243,7 @@ module Dynflow
243
243
 
244
244
  def load(what, condition)
245
245
  table = table(what)
246
- if (record = with_retry { table.first(condition.symbolize_keys) } )
246
+ if (record = with_retry { table.first(Utils.symbolize_keys(condition)) } )
247
247
  load_data(record)
248
248
  else
249
249
  raise KeyError, "searching: #{what} by: #{condition.inspect}"
@@ -251,16 +251,16 @@ module Dynflow
251
251
  end
252
252
 
253
253
  def load_data(record)
254
- HashWithIndifferentAccess.new(MultiJson.load(record[:data]))
254
+ Utils.indifferent_hash(MultiJson.load(record[:data]))
255
255
  end
256
256
 
257
257
  def delete(what, condition)
258
- table(what).where(condition.symbolize_keys).delete
258
+ table(what).where(Utils.symbolize_keys(condition)).delete
259
259
  end
260
260
 
261
261
  def extract_metadata(what, value)
262
262
  meta_keys = META_DATA.fetch(what)
263
- value = value.with_indifferent_access
263
+ value = Utils.indifferent_hash(value)
264
264
  meta_keys.inject({}) { |h, k| h.update k.to_sym => value[k] }
265
265
  end
266
266
 
@@ -312,7 +312,7 @@ module Dynflow
312
312
  raise ArgumentError, "unkown columns: #{unknown.inspect}"
313
313
  end
314
314
 
315
- data_set.where filters.symbolize_keys
315
+ data_set.where Utils.symbolize_keys(filters)
316
316
  end
317
317
 
318
318
  def with_retry
@@ -0,0 +1,5 @@
1
+ Sequel.migration do
2
+ change do
3
+ rename_table(:dynflow_scheduled_plans, :dynflow_delayed_plans)
4
+ end
5
+ end
@@ -27,7 +27,7 @@ module Dynflow
27
27
  end
28
28
 
29
29
  def self.constantize(action_name)
30
- action_name.constantize
30
+ Utils.constantize(action_name)
31
31
  end
32
32
 
33
33
  private_class_method :check_class_matching, :check_class_key_present
@@ -19,7 +19,7 @@ module Dynflow
19
19
  end
20
20
 
21
21
  if (type_name = other[ARBITRARY_TYPE_KEY] || other[ARBITRARY_TYPE_KEY.to_s])
22
- type = type_name.constantize rescue nil
22
+ type = Utils.constantize(type_name) rescue nil
23
23
  if type && type.respond_to?(:from_hash)
24
24
  return type.from_hash other
25
25
  end
@@ -49,7 +49,7 @@ module Dynflow
49
49
  Match! action.phase, Action::Plan
50
50
  Match! action.state, :success
51
51
  action.execution_plan.planned_run_steps.must_include action
52
- action.input.must_equal input.stringify_keys if input
52
+ action.input.must_equal Utils.stringify_keys(input) if input
53
53
  block.call action.input if block
54
54
  end
55
55
 
@@ -0,0 +1,205 @@
1
+ module Dynflow
2
+ module Utils
3
+
4
+ def self.symbolize_keys(hash)
5
+ return hash.symbolize_keys if hash.respond_to?(:symbolize_keys)
6
+ hash.reduce({}) do |new_hash, (key, value)|
7
+ new_hash.update(key.to_sym => value)
8
+ end
9
+ end
10
+
11
+ def self.stringify_keys(hash)
12
+ return hash.stringify_keys if hash.respond_to?(:stringify_keys)
13
+ hash.reduce({}) do |new_hash, (key, value)|
14
+ new_hash.update(key.to_s => value)
15
+ end
16
+ end
17
+
18
+ # Inspired by ActiveSupport::Inflector
19
+ def self.constantize(string)
20
+ return string.constantize if string.respond_to?(:constantize)
21
+
22
+ names = string.split('::')
23
+
24
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
25
+ Object.const_get(string) if names.empty?
26
+
27
+ # Remove the first blank element in case of '::ClassName' notation.
28
+ names.shift if names.size > 1 && names.first.empty?
29
+
30
+ names.inject(Object) do |constant, name|
31
+ if constant == Object
32
+ constant.const_get(name)
33
+ else
34
+ candidate = constant.const_get(name)
35
+ next candidate if constant.const_defined?(name, false)
36
+ next candidate unless Object.const_defined?(name)
37
+
38
+ # Go down the ancestors to check if it is owned directly. The check
39
+ # stops when we reach Object or the end of ancestors tree.
40
+ constant = constant.ancestors.inject do |const, ancestor|
41
+ break const if ancestor == Object
42
+ break ancestor if ancestor.const_defined?(name, false)
43
+ const
44
+ end
45
+
46
+ # owner is in Object, so raise
47
+ constant.const_get(name, false)
48
+ end
49
+ end
50
+ end
51
+
52
+ def self.indifferent_hash(hash)
53
+ if defined? ::HashWithIndifferentAccess
54
+ # the users already have it: lets give them what they are used to
55
+ ::HashWithIndifferentAccess.new(hash)
56
+ else
57
+ if hash.is_a? IndifferentHash
58
+ return hash
59
+ else
60
+ IndifferentHash.new(hash)
61
+ end
62
+ end
63
+ end
64
+
65
+ # Heaviliy inpired by ActiveSupport::HashWithIndifferentAccess,
66
+ # reasons we don't want to use the original implementation:
67
+ # 1. we don't want any core_ext extensions
68
+ # 2. some users are not happy about seeing the ActiveSupport as
69
+ # our depednency
70
+ class IndifferentHash < Hash
71
+ def initialize(constructor = {})
72
+ if constructor.respond_to?(:to_hash)
73
+ super()
74
+ update(constructor)
75
+ else
76
+ super(constructor)
77
+ end
78
+ end
79
+
80
+ def default(key = nil)
81
+ if key.is_a?(Symbol) && include?(key = key.to_s)
82
+ self[key]
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def self.[](*args)
89
+ new.merge!(Hash[*args])
90
+ end
91
+
92
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
93
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
94
+
95
+ def []=(key, value)
96
+ regular_writer(convert_key(key), convert_value(value, for: :assignment))
97
+ end
98
+
99
+ alias_method :store, :[]=
100
+
101
+ def update(other_hash)
102
+ if other_hash.is_a? IndifferentHash
103
+ super(other_hash)
104
+ else
105
+ other_hash.to_hash.each_pair do |key, value|
106
+ if block_given? && key?(key)
107
+ value = yield(convert_key(key), self[key], value)
108
+ end
109
+ regular_writer(convert_key(key), convert_value(value))
110
+ end
111
+ self
112
+ end
113
+ end
114
+
115
+ alias_method :merge!, :update
116
+
117
+ def key?(key)
118
+ super(convert_key(key))
119
+ end
120
+
121
+ alias_method :include?, :key?
122
+ alias_method :has_key?, :key?
123
+ alias_method :member?, :key?
124
+
125
+ def fetch(key, *extras)
126
+ super(convert_key(key), *extras)
127
+ end
128
+
129
+ def values_at(*indices)
130
+ indices.collect { |key| self[convert_key(key)] }
131
+ end
132
+
133
+ def dup
134
+ self.class.new(self).tap do |new_hash|
135
+ new_hash.default = default
136
+ end
137
+ end
138
+
139
+ def merge(hash, &block)
140
+ self.dup.update(hash, &block)
141
+ end
142
+
143
+ def reverse_merge(other_hash)
144
+ super(self.class.new_from_hash_copying_default(other_hash))
145
+ end
146
+
147
+ def reverse_merge!(other_hash)
148
+ replace(reverse_merge( other_hash ))
149
+ end
150
+
151
+ def replace(other_hash)
152
+ super(self.class.new_from_hash_copying_default(other_hash))
153
+ end
154
+
155
+ def delete(key)
156
+ super(convert_key(key))
157
+ end
158
+
159
+ def stringify_keys!; self end
160
+ def deep_stringify_keys!; self end
161
+ def stringify_keys; dup end
162
+ def deep_stringify_keys; dup end
163
+ def to_options!; self end
164
+
165
+ def select(*args, &block)
166
+ dup.tap { |hash| hash.select!(*args, &block) }
167
+ end
168
+
169
+ def reject(*args, &block)
170
+ dup.tap { |hash| hash.reject!(*args, &block) }
171
+ end
172
+
173
+ # Convert to a regular hash with string keys.
174
+ def to_hash
175
+ _new_hash = Hash.new(default)
176
+ each do |key, value|
177
+ _new_hash[key] = convert_value(value, for: :to_hash)
178
+ end
179
+ _new_hash
180
+ end
181
+
182
+ protected
183
+ def convert_key(key)
184
+ key.kind_of?(Symbol) ? key.to_s : key
185
+ end
186
+
187
+ def convert_value(value, options = {})
188
+ if value.is_a? Hash
189
+ if options[:for] == :to_hash
190
+ value.to_hash
191
+ else
192
+ Utils.indifferent_hash(value)
193
+ end
194
+ elsif value.is_a?(Array)
195
+ unless options[:for] == :assignment
196
+ value = value.dup
197
+ end
198
+ value.map! { |e| convert_value(e, options) }
199
+ else
200
+ value
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end