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.
- checksums.yaml +4 -4
- data/.travis.yml +12 -11
- data/doc/pages/Gemfile +2 -0
- data/doc/pages/README.md +48 -0
- data/doc/pages/_config.yml +1 -1
- data/doc/pages/plugins/plantuml.rb +1 -1
- data/doc/pages/source/documentation/index.md +96 -1
- data/lib/dynflow/action.rb +13 -0
- data/lib/dynflow/delayed_plan.rb +13 -5
- data/lib/dynflow/execution_plan.rb +18 -0
- data/lib/dynflow/execution_plan/hooks.rb +91 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +13 -13
- data/lib/dynflow/persistence.rb +11 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +8 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +87 -28
- data/lib/dynflow/persistence_adapters/sequel_migrations/011_add_uuid_column.rb +61 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb +8 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb +16 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb +13 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb +18 -0
- data/lib/dynflow/serializable.rb +2 -1
- data/lib/dynflow/serializers/abstract.rb +34 -5
- data/lib/dynflow/version.rb +1 -1
- data/test/batch_sub_tasks_test.rb +3 -0
- data/test/concurrency_control_test.rb +6 -2
- data/test/execution_plan_hooks_test.rb +86 -0
- data/test/executor_test.rb +5 -2
- data/test/future_execution_test.rb +37 -0
- data/test/persistence_test.rb +144 -29
- metadata +11 -2
@@ -75,19 +75,19 @@ module Dynflow
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def to_hash
|
78
|
-
recursive_to_hash
|
79
|
-
id:
|
80
|
-
state:
|
81
|
-
class:
|
82
|
-
action_class:
|
83
|
-
action_id:
|
84
|
-
error:
|
85
|
-
started_at:
|
86
|
-
ended_at:
|
87
|
-
execution_time:
|
88
|
-
real_time:
|
89
|
-
progress_done:
|
90
|
-
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
|
data/lib/dynflow/persistence.rb
CHANGED
@@ -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(
|
56
|
-
order(
|
57
|
-
paginate(table(
|
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
|
-
|
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
|
-
|
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
|
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
|
287
|
-
|
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
|
-
|
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
|
-
|
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,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
|
data/lib/dynflow/serializable.rb
CHANGED
@@ -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)
|