dynflow 0.8.35 → 0.8.36

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