dynflow 0.8.35 → 0.8.36

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.
@@ -75,19 +75,19 @@ module Dynflow
75
75
  end
76
76
 
77
77
  def to_hash
78
- recursive_to_hash execution_plan_id: execution_plan_id,
79
- id: id,
80
- state: state,
81
- class: self.class.to_s,
82
- action_class: action_class.to_s,
83
- action_id: action_id,
84
- error: error,
85
- started_at: time_to_str(started_at),
86
- ended_at: time_to_str(ended_at),
87
- execution_time: execution_time,
88
- real_time: real_time,
89
- progress_done: progress_done,
90
- progress_weight: progress_weight
78
+ recursive_to_hash execution_plan_uuid: execution_plan_id,
79
+ id: id,
80
+ state: state,
81
+ class: self.class.to_s,
82
+ action_class: action_class.to_s,
83
+ action_id: action_id,
84
+ error: error,
85
+ started_at: started_at,
86
+ ended_at: ended_at,
87
+ execution_time: execution_time,
88
+ real_time: real_time,
89
+ progress_done: progress_done,
90
+ progress_weight: progress_weight
91
91
  end
92
92
 
93
93
  def progress_done
@@ -23,6 +23,13 @@ module Dynflow
23
23
  return Action.from_hash(attributes, step.world)
24
24
  end
25
25
 
26
+ def load_actions(execution_plan, action_ids)
27
+ attributes = adapter.load_actions(execution_plan.id, action_ids)
28
+ attributes.map do |action|
29
+ Action.from_hash(action.merge(phase: Action::Present, execution_plan: execution_plan), @world)
30
+ end
31
+ end
32
+
26
33
  def load_action_for_presentation(execution_plan, action_id, step = nil)
27
34
  attributes = adapter.load_action(execution_plan.id, action_id)
28
35
  Action.from_hash(attributes.update(phase: Action::Present, execution_plan: execution_plan, step: step), @world).tap do |present_action|
@@ -30,6 +37,10 @@ module Dynflow
30
37
  end
31
38
  end
32
39
 
40
+ def load_actions_attributes(execution_plan_id, attributes)
41
+ adapter.load_actions_attributes(execution_plan_id, attributes).reject(&:empty?)
42
+ end
43
+
33
44
  def save_action(execution_plan_id, action)
34
45
  adapter.save_action(execution_plan_id, action.id, action.to_hash)
35
46
  end
@@ -91,6 +91,14 @@ module Dynflow
91
91
  raise NotImplementedError
92
92
  end
93
93
 
94
+ def load_actions_attributes(execution_plan_id, attributes)
95
+ raise NotImplementedError
96
+ end
97
+
98
+ def load_actions(execution_plan_id, action_ids)
99
+ raise NotImplementedError
100
+ end
101
+
94
102
  def save_action(execution_plan_id, action_id, value)
95
103
  raise NotImplementedError
96
104
  end
@@ -7,6 +7,7 @@ module Dynflow
7
7
  module PersistenceAdapters
8
8
 
9
9
  Sequel.extension :migration
10
+ Sequel.database_timezone = :utc
10
11
 
11
12
  class Sequel < Abstract
12
13
  include Algebrick::TypeCheck
@@ -29,13 +30,19 @@ module Dynflow
29
30
  META_DATA.fetch :execution_plan
30
31
  end
31
32
 
32
- META_DATA = { execution_plan: %w(label state result started_at ended_at real_time execution_time),
33
- action: %w(caller_execution_plan_id caller_action_id),
34
- step: %w(state started_at ended_at real_time execution_time action_id progress_done progress_weight),
33
+ META_DATA = { execution_plan: %w(label state result started_at ended_at real_time execution_time root_plan_step_id class),
34
+ action: %w(caller_execution_plan_id caller_action_id class plan_step_id run_step_id finalize_step_id),
35
+ step: %w(state started_at ended_at real_time execution_time action_id progress_done progress_weight
36
+ class action_class execution_plan_uuid),
35
37
  envelope: %w(receiver_id),
36
38
  coordinator_record: %w(id owner_id class),
37
39
  delayed: %w(execution_plan_uuid start_at start_before args_serializer)}
38
40
 
41
+ SERIALIZABLE_COLUMNS = { action: %w(input output),
42
+ delayed: %w(serialized_args),
43
+ execution_plan: %w(run_flow finalize_flow execution_history step_ids),
44
+ step: %w(error children) }
45
+
39
46
  def initialize(config)
40
47
  config = config.dup
41
48
  @additional_responsibilities = { coordinator: true, connector: true }
@@ -51,13 +58,14 @@ module Dynflow
51
58
  end
52
59
 
53
60
  def find_execution_plans(options = {})
61
+ table_name = :execution_plan
54
62
  options[:order_by] ||= :started_at
55
- data_set = filter(:execution_plan,
56
- order(:execution_plan,
57
- paginate(table(:execution_plan), options),
63
+ data_set = filter(table_name,
64
+ order(table_name,
65
+ paginate(table(table_name), options),
58
66
  options),
59
67
  options[:filters])
60
- data_set.all.map { |record| load_data(record) }
68
+ data_set.all.map { |record| execution_plan_column_map(load_data(record, table_name)) }
61
69
  end
62
70
 
63
71
  def find_execution_plan_counts(options = {})
@@ -88,11 +96,11 @@ module Dynflow
88
96
  end
89
97
 
90
98
  def load_execution_plan(execution_plan_id)
91
- load :execution_plan, uuid: execution_plan_id
99
+ execution_plan_column_map(load :execution_plan, uuid: execution_plan_id)
92
100
  end
93
101
 
94
102
  def save_execution_plan(execution_plan_id, value)
95
- save :execution_plan, { uuid: execution_plan_id }, value
103
+ save :execution_plan, { uuid: execution_plan_id }, value, false
96
104
  end
97
105
 
98
106
  def delete_delayed_plans(filters, batch_size = 1000)
@@ -107,17 +115,19 @@ module Dynflow
107
115
  end
108
116
 
109
117
  def find_old_execution_plans(age)
110
- table(:execution_plan)
118
+ table_name = :execution_plan
119
+ table(table_name)
111
120
  .where(::Sequel.lit('ended_at <= ? AND state = ?', age, 'stopped'))
112
- .all.map { |plan| load_data plan }
121
+ .all.map { |plan| execution_plan_column_map(load_data plan, table_name) }
113
122
  end
114
123
 
115
124
  def find_past_delayed_plans(time)
116
- table(:delayed)
125
+ table_name = :delayed
126
+ table(table_name)
117
127
  .where(::Sequel.lit('start_at <= ? OR (start_before IS NOT NULL AND start_before <= ?)', time, time))
118
128
  .order_by(:start_at)
119
129
  .all
120
- .map { |plan| load_data(plan) }
130
+ .map { |plan| load_data(plan, table_name) }
121
131
  end
122
132
 
123
133
  def load_delayed_plan(execution_plan_id)
@@ -127,7 +137,7 @@ module Dynflow
127
137
  end
128
138
 
129
139
  def save_delayed_plan(execution_plan_id, value)
130
- save :delayed, { execution_plan_uuid: execution_plan_id }, value
140
+ save :delayed, { execution_plan_uuid: execution_plan_id }, value, false
131
141
  end
132
142
 
133
143
  def load_step(execution_plan_id, step_id)
@@ -139,15 +149,23 @@ module Dynflow
139
149
  end
140
150
 
141
151
  def save_step(execution_plan_id, step_id, value)
142
- save :step, { execution_plan_uuid: execution_plan_id, id: step_id }, value
152
+ save :step, { execution_plan_uuid: execution_plan_id, id: step_id }, value, false
143
153
  end
144
154
 
145
155
  def load_action(execution_plan_id, action_id)
146
156
  load :action, execution_plan_uuid: execution_plan_id, id: action_id
147
157
  end
148
158
 
159
+ def load_actions(execution_plan_id, action_ids)
160
+ load_records :action, { execution_plan_uuid: execution_plan_id, id: action_ids }
161
+ end
162
+
163
+ def load_actions_attributes(execution_plan_id, attributes)
164
+ load_records :action, { execution_plan_uuid: execution_plan_id }, attributes
165
+ end
166
+
149
167
  def save_action(execution_plan_id, action_id, value)
150
- save :action, { execution_plan_uuid: execution_plan_id, id: action_id }, value
168
+ save :action, { execution_plan_uuid: execution_plan_id, id: action_id }, value, false
151
169
  end
152
170
 
153
171
  def connector_feature!
@@ -242,22 +260,37 @@ module Dynflow
242
260
  ::Sequel::Migrator.run(db, self.class.migrations_path, table: 'dynflow_schema_info')
243
261
  end
244
262
 
245
- def prepare_record(table_name, value, base = {})
263
+ def prepare_record(table_name, value, base = {}, with_data = true)
246
264
  record = base.dup
247
- if table(table_name).columns.include?(:data)
265
+ if with_data && table(table_name).columns.include?(:data)
248
266
  record[:data] = dump_data(value)
267
+ else
268
+ record[:data] = nil
269
+ record.merge! serialize_columns(table_name, value)
249
270
  end
271
+
250
272
  record.merge! extract_metadata(table_name, value)
251
273
  record.each { |k, v| record[k] = v.to_s if v.is_a? Symbol }
274
+
252
275
  record
253
276
  end
254
277
 
255
- def save(what, condition, value)
278
+ def serialize_columns(table_name, record)
279
+ record.reduce({}) do |acc, (key, value)|
280
+ if SERIALIZABLE_COLUMNS.fetch(table_name, []).include?(key.to_s)
281
+ acc.merge(key.to_sym => dump_data(value))
282
+ else
283
+ acc
284
+ end
285
+ end
286
+ end
287
+
288
+ def save(what, condition, value, with_data = true)
256
289
  table = table(what)
257
290
  existing_record = with_retry { table.first condition } unless condition.empty?
258
291
 
259
292
  if value
260
- record = prepare_record(what, value, (existing_record || condition))
293
+ record = prepare_record(what, value, (existing_record || condition), with_data)
261
294
  if existing_record
262
295
  with_retry { table.where(condition).update(record) }
263
296
  else
@@ -273,7 +306,7 @@ module Dynflow
273
306
  def load_record(what, condition)
274
307
  table = table(what)
275
308
  if (record = with_retry { table.first(Utils.symbolize_keys(condition)) } )
276
- load_data(record)
309
+ load_data(record, what)
277
310
  else
278
311
  raise KeyError, "searching: #{what} by: #{condition.inspect}"
279
312
  end
@@ -281,14 +314,34 @@ module Dynflow
281
314
 
282
315
  alias_method :load, :load_record
283
316
 
284
- def load_records(what, condition)
317
+ def load_records(what, condition, keys = nil)
285
318
  table = table(what)
286
- records = with_retry { table.filter(Utils.symbolize_keys(condition)).all }
287
- records.map { |record| load_data(record) }
319
+ records = with_retry do
320
+ filtered = table.filter(Utils.symbolize_keys(condition))
321
+ # Filter out requested columns which the table doesn't have, load data just in case
322
+ filtered = filtered.select(:data, *(table.columns & keys)) unless keys.nil?
323
+ filtered.all
324
+ end
325
+ records = records.map { |record| load_data(record, what) }
326
+ return records if keys.nil?
327
+ records.map do |record|
328
+ keys.reduce({}) do |acc, key|
329
+ acc.merge(key => record[key])
330
+ end
331
+ end
288
332
  end
289
333
 
290
- def load_data(record)
291
- Utils.indifferent_hash(MultiJson.load(record[:data]))
334
+ def load_data(record, what = nil)
335
+ hash = if record[:data].nil?
336
+ SERIALIZABLE_COLUMNS.fetch(what, []).each do |key|
337
+ key = key.to_sym
338
+ record[key] = MultiJson.load(record[key]) unless record[key].nil?
339
+ end
340
+ record
341
+ else
342
+ MultiJson.load(record[:data])
343
+ end
344
+ Utils.indifferent_hash(hash)
292
345
  end
293
346
 
294
347
  def ensure_backup_dir(backup_dir)
@@ -314,13 +367,14 @@ module Dynflow
314
367
  end
315
368
 
316
369
  def extract_metadata(what, value)
317
- meta_keys = META_DATA.fetch(what)
370
+ meta_keys = META_DATA.fetch(what) - SERIALIZABLE_COLUMNS.fetch(what, [])
318
371
  value = Utils.indifferent_hash(value)
319
372
  meta_keys.inject({}) { |h, k| h.update k.to_sym => value[k] }
320
373
  end
321
374
 
322
375
  def dump_data(value)
323
- MultiJson.dump Type!(value, Hash)
376
+ return if value.nil?
377
+ MultiJson.dump Type!(value, Hash, Array)
324
378
  end
325
379
 
326
380
  def paginate(data_set, options)
@@ -395,6 +449,11 @@ module Dynflow
395
449
  end
396
450
  end
397
451
  end
452
+
453
+ def execution_plan_column_map(plan)
454
+ plan[:id] = plan[:uuid] unless plan[:uuid].nil?
455
+ plan
456
+ end
398
457
  end
399
458
  end
400
459
  end
@@ -0,0 +1,61 @@
1
+ def to_uuid(table_name, column_name)
2
+ set_column_type(table_name, column_name, :uuid, :using => "#{column_name}::uuid")
3
+ end
4
+
5
+ def from_uuid(table_name, column_name)
6
+ set_column_type table_name, column_name, String, primary_key: true, size: 36, fixed: true
7
+ end
8
+
9
+ def with_foreign_key_recreation(&block)
10
+ # Drop the foreign key constraints so we can change the column type
11
+ alter_table :dynflow_actions do
12
+ drop_foreign_key [:execution_plan_uuid]
13
+ end
14
+ alter_table :dynflow_steps do
15
+ drop_foreign_key [:execution_plan_uuid]
16
+ drop_foreign_key [:execution_plan_uuid, :action_id], :name => :dynflow_steps_execution_plan_uuid_fkey1
17
+ end
18
+ alter_table :dynflow_delayed_plans do
19
+ drop_foreign_key [:execution_plan_uuid]
20
+ end
21
+
22
+ block.call
23
+
24
+ # Recreat the foreign key constraints as they were before
25
+ alter_table :dynflow_actions do
26
+ add_foreign_key [:execution_plan_uuid], :dynflow_execution_plans
27
+ end
28
+ alter_table :dynflow_steps do
29
+ add_foreign_key [:execution_plan_uuid], :dynflow_execution_plans
30
+ add_foreign_key [:execution_plan_uuid, :action_id], :dynflow_actions,
31
+ :name => :dynflow_steps_execution_plan_uuid_fkey1
32
+ end
33
+ alter_table :dynflow_delayed_plans do
34
+ add_foreign_key [:execution_plan_uuid], :dynflow_execution_plans,
35
+ :name => :dynflow_scheduled_plans_execution_plan_uuid_fkey
36
+ end
37
+ end
38
+
39
+ Sequel.migration do
40
+ up do
41
+ if database_type == :postgresql
42
+ with_foreign_key_recreation do
43
+ to_uuid :dynflow_execution_plans, :uuid
44
+ to_uuid :dynflow_actions, :execution_plan_uuid
45
+ to_uuid :dynflow_steps, :execution_plan_uuid
46
+ to_uuid :dynflow_delayed_plans, :execution_plan_uuid
47
+ end
48
+ end
49
+ end
50
+
51
+ down do
52
+ if database_type == :postgresql
53
+ with_foreign_key_recreation do
54
+ from_uuid :dynflow_execution_plans, :uuid
55
+ from_uuid :dynflow_actions, :execution_plan_uuid
56
+ from_uuid :dynflow_steps, :execution_plan_uuid
57
+ from_uuid :dynflow_delayed_plans, :execution_plan_uuid
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:dynflow_delayed_plans) do
4
+ long_text_type = @db.database_type == :mysql ? :mediumtext : String
5
+ add_column :serialized_args, long_text_type
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:dynflow_actions) do
4
+ long_text_type = @db.database_type == :mysql ? :mediumtext : String
5
+ add_column :class, String
6
+ add_column :input, long_text_type
7
+ add_column :output, long_text_type
8
+
9
+ # These could be removed in the future because an action can have at most one of each
10
+ # and each belongs to an action
11
+ add_column :plan_step_id, Integer
12
+ add_column :run_step_id, Integer
13
+ add_column :finalize_step_id, Integer
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:dynflow_steps) do
4
+ add_column :class, String
5
+ add_column :error, @db.database_type == :mysql ? :mediumtext : String
6
+
7
+ # These could be removed in the future because an action can have at most one of each
8
+ # and each belongs to an action
9
+ add_column :action_class, String
10
+ add_column :children, String
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:dynflow_execution_plans) do
4
+ long_text_type = @db.database_type == :mysql ? :mediumtext : String
5
+
6
+ add_column :class, String
7
+
8
+ add_column :run_flow, long_text_type
9
+ add_column :finalize_flow, long_text_type
10
+ add_column :execution_history, long_text_type
11
+
12
+ # These could be removed in the future because an action can have at most one of each
13
+ # and each belongs to an action
14
+ add_column :root_plan_step_id, Integer
15
+ add_column :step_ids, long_text_type
16
+ end
17
+ end
18
+ end
@@ -40,7 +40,7 @@ module Dynflow
40
40
  if values.size == 1
41
41
  value = values.first
42
42
  case value
43
- when String, Numeric, Symbol, TrueClass, FalseClass, NilClass
43
+ when String, Numeric, Symbol, TrueClass, FalseClass, NilClass, Time
44
44
  value
45
45
  when Hash
46
46
  value.inject({}) { |h, (k, v)| h.update k => recursive_to_hash(v) }
@@ -57,6 +57,7 @@ module Dynflow
57
57
 
58
58
  def self.string_to_time(string)
59
59
  return if string.nil?
60
+ return string if string.is_a?(Time)
60
61
  _, year, month, day, hour, min, sec =
61
62
  */(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/.match(string)
62
63
  Time.utc(year.to_i, month.to_i, day.to_i, hour.to_i, min.to_i, sec.to_i)