dynflow 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
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