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
@@ -1,37 +1,66 @@
|
|
1
1
|
module Dynflow
|
2
2
|
module Serializers
|
3
|
+
# @abstract
|
4
|
+
# Used to serialize and deserialize arguments for storage in a database.
|
5
|
+
# Used by {DelayedPlan} to store arguments which should be passed into
|
6
|
+
# the {Dynflow::Action}'s #plan method when the plan is executed.
|
3
7
|
class Abstract
|
4
8
|
|
5
9
|
attr_reader :args, :serialized_args
|
6
10
|
|
11
|
+
# @param args [Array] arguments to be serialized
|
12
|
+
# @param serialized_args [nil, Array] arguments in their serialized form
|
7
13
|
def initialize(args, serialized_args = nil)
|
8
14
|
@args = args
|
9
15
|
@serialized_args = serialized_args
|
10
16
|
end
|
11
17
|
|
12
|
-
|
18
|
+
# Retrieves the arguments
|
19
|
+
#
|
20
|
+
# @raise [RuntimeError] if the deserialized arguments are not available
|
21
|
+
# @return [Array] the arguments
|
22
|
+
def args!
|
13
23
|
raise "@args not set" if @args.nil?
|
14
24
|
return @args
|
15
25
|
end
|
16
26
|
|
17
|
-
|
27
|
+
# Retrieves the arguments in the serialized form
|
28
|
+
#
|
29
|
+
# @raise [RuntimeError] if the serialized arguments are not available
|
30
|
+
# @return [Array] the serialized arguments
|
31
|
+
def serialized_args!
|
18
32
|
raise "@serialized_args not set" if @serialized_args.nil?
|
19
33
|
return @serialized_args
|
20
34
|
end
|
21
35
|
|
36
|
+
# Converts arguments into their serialized form, iterates over deserialized
|
37
|
+
# arguments, applying {#serialize} to each of them
|
38
|
+
#
|
39
|
+
# @raise [RuntimeError] if the deserialized arguments are not available
|
40
|
+
# @return [Array] the serialized arguments
|
22
41
|
def perform_serialization!
|
23
|
-
@serialized_args = args
|
42
|
+
@serialized_args = args!.map { |arg| serialize arg }
|
24
43
|
end
|
25
44
|
|
45
|
+
# Converts arguments into their deserialized form, iterates over serialized
|
46
|
+
# arguments, applying {#deserialize} to each of them
|
47
|
+
#
|
48
|
+
# @raise [RuntimeError] if the serialized arguments are not available
|
49
|
+
# @return [Array] the deserialized arguments
|
26
50
|
def perform_deserialization!
|
27
|
-
|
28
|
-
@args = serialized_args.map { |arg| deserialize arg }
|
51
|
+
@args = serialized_args!.map { |arg| deserialize arg }
|
29
52
|
end
|
30
53
|
|
54
|
+
# Converts an argument into it serialized form
|
55
|
+
#
|
56
|
+
# @param arg the argument to be serialized
|
31
57
|
def serialize(arg)
|
32
58
|
raise NotImplementedError
|
33
59
|
end
|
34
60
|
|
61
|
+
# Converts a serialized argument into its deserialized form
|
62
|
+
#
|
63
|
+
# @param arg the argument to be deserialized
|
35
64
|
def deserialize(arg)
|
36
65
|
raise NotImplementedError
|
37
66
|
end
|
data/lib/dynflow/version.rb
CHANGED
@@ -69,6 +69,7 @@ module Dynflow
|
|
69
69
|
plan = world.plan(ParentAction, 20)
|
70
70
|
future = world.execute plan.id
|
71
71
|
wait_for { future.completed? }
|
72
|
+
plan = world.persistence.load_execution_plan(plan.id)
|
72
73
|
action = plan.entry_action
|
73
74
|
|
74
75
|
action.output[:batch_count].must_equal action.total_count / action.batch_size
|
@@ -79,6 +80,7 @@ module Dynflow
|
|
79
80
|
plan = world.plan(ParentAction, 20)
|
80
81
|
future = world.execute plan.id
|
81
82
|
wait_for { future.completed? }
|
83
|
+
plan = world.persistence.load_execution_plan(plan.id)
|
82
84
|
action = plan.entry_action
|
83
85
|
action.output[:batch_count].must_equal 1
|
84
86
|
future.value.state.must_equal :paused
|
@@ -97,6 +99,7 @@ module Dynflow
|
|
97
99
|
plan = world.plan(ParentAction, 10)
|
98
100
|
future = world.execute plan.id
|
99
101
|
wait_for { future.completed? }
|
102
|
+
plan = world.persistence.load_execution_plan(plan.id)
|
100
103
|
action = plan.entry_action
|
101
104
|
action.send(:can_spawn_next_batch?).must_equal false
|
102
105
|
action.current_batch.must_be :empty?
|
@@ -146,6 +146,7 @@ module Dynflow
|
|
146
146
|
wait_for { plan.sub_plans_count == total }
|
147
147
|
world.event(plan.id, plan.steps.values.last.id, ::Dynflow::Action::Cancellable::Cancel)
|
148
148
|
wait_for { triggered.completed? }
|
149
|
+
plan = world.persistence.load_execution_plan(plan.id)
|
149
150
|
plan.entry_action.output[:failed_count].must_equal total
|
150
151
|
world.throttle_limiter.core.ask!(:running).max.must_be :<=, 0
|
151
152
|
end
|
@@ -154,10 +155,13 @@ module Dynflow
|
|
154
155
|
it 'calculates time interval correctly' do
|
155
156
|
world.stub :clock, klok do
|
156
157
|
total = 10
|
157
|
-
get_interval = ->(plan)
|
158
|
+
get_interval = ->(plan) do
|
159
|
+
plan = world.persistence.load_execution_plan(plan.id)
|
160
|
+
plan.entry_action.input[:concurrency_control][:time][:meta][:interval]
|
161
|
+
end
|
158
162
|
|
159
163
|
plan = world.plan(ParentAction, total, 1, 10)
|
160
|
-
|
164
|
+
world.execute(plan.id)
|
161
165
|
wait_for { plan.sub_plans_count == total }
|
162
166
|
wait_for { klok.progress; plan.sub_plans.all? { |sub| successful? sub } }
|
163
167
|
# 10 tasks over 10 seconds, one task at a time, 1 task every second
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module Dynflow
|
4
|
+
class ExecutionPlan
|
5
|
+
describe Hooks do
|
6
|
+
include PlanAssertions
|
7
|
+
|
8
|
+
let(:world) { WorldFactory.create_world }
|
9
|
+
|
10
|
+
class Flag
|
11
|
+
class << self
|
12
|
+
def raise!
|
13
|
+
@raised = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def raised?
|
17
|
+
@raised
|
18
|
+
end
|
19
|
+
|
20
|
+
def lower!
|
21
|
+
@raised = false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module FlagHook
|
27
|
+
def raise_flag(_execution_plan)
|
28
|
+
Flag.raise!
|
29
|
+
end
|
30
|
+
|
31
|
+
def controlled_failure(_execution_plan)
|
32
|
+
Flag.raise!
|
33
|
+
raise "A controlled failure"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ActionWithHooks < ::Dynflow::Action
|
38
|
+
include FlagHook
|
39
|
+
|
40
|
+
execution_plan_hooks.use :raise_flag, :on => :success
|
41
|
+
end
|
42
|
+
|
43
|
+
class ActionOnStop < ::Dynflow::Action
|
44
|
+
include FlagHook
|
45
|
+
|
46
|
+
execution_plan_hooks.use :controlled_failure, :on => :stopped
|
47
|
+
end
|
48
|
+
|
49
|
+
class Inherited < ActionWithHooks; end
|
50
|
+
class Overriden < ActionWithHooks
|
51
|
+
execution_plan_hooks.do_not_use :raise_flag
|
52
|
+
end
|
53
|
+
|
54
|
+
before { Flag.lower! }
|
55
|
+
|
56
|
+
it 'runs the on_success hook' do
|
57
|
+
refute Flag.raised?
|
58
|
+
plan = world.trigger(ActionWithHooks)
|
59
|
+
plan.finished.wait!
|
60
|
+
assert Flag.raised?
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'does not alter the execution plan when exception happens in the hook' do
|
64
|
+
refute Flag.raised?
|
65
|
+
plan = world.plan(ActionOnStop)
|
66
|
+
plan = world.execute(plan.id).wait!.value
|
67
|
+
assert Flag.raised?
|
68
|
+
plan.result.must_equal :success
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'inherits the hooks when subclassing' do
|
72
|
+
refute Flag.raised?
|
73
|
+
plan = world.trigger(Inherited)
|
74
|
+
plan.finished.wait!
|
75
|
+
assert Flag.raised?
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'can override the hooks from the child' do
|
79
|
+
refute Flag.raised?
|
80
|
+
plan = world.trigger(Overriden)
|
81
|
+
plan.finished.wait!
|
82
|
+
refute Flag.raised?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/test/executor_test.rb
CHANGED
@@ -221,8 +221,11 @@ module Dynflow
|
|
221
221
|
result.started_at.wont_be_nil
|
222
222
|
result.ended_at.wont_be_nil
|
223
223
|
result.execution_time.must_be :<, result.real_time
|
224
|
-
|
225
|
-
|
224
|
+
|
225
|
+
step_sum = result.steps.values.map(&:execution_time).reduce(:+)
|
226
|
+
|
227
|
+
# Storing floats can lead to slight deviations, 1ns precision should be enough
|
228
|
+
result.execution_time.must_be_close_to step_sum, 0.000_001
|
226
229
|
|
227
230
|
plan_step = result.steps[1]
|
228
231
|
plan_step.started_at.wont_be_nil
|
@@ -115,6 +115,9 @@ module Dynflow
|
|
115
115
|
end
|
116
116
|
|
117
117
|
describe 'serializers' do
|
118
|
+
let(:args) { %w(arg1 arg2) }
|
119
|
+
let(:serialized_serializer) { Dynflow::Serializers::Noop.new(nil, args) }
|
120
|
+
let(:deserialized_serializer) { Dynflow::Serializers::Noop.new(args, nil) }
|
118
121
|
let(:save_and_load) do
|
119
122
|
->(thing) { MultiJson.load(MultiJson.dump(thing)) }
|
120
123
|
end
|
@@ -134,6 +137,40 @@ module Dynflow
|
|
134
137
|
input = [1, 2.0, 'three', ['four-1', 'four-2'], { 'five' => 5 }]
|
135
138
|
simulated_use.call(Dynflow::Serializers::Noop, input).must_equal input
|
136
139
|
end
|
140
|
+
|
141
|
+
it 'args! raises if not deserialized' do
|
142
|
+
proc { serialized_serializer.args! }.must_raise RuntimeError
|
143
|
+
deserialized_serializer.args! # Must not raise
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'serialized_args! raises if not serialized' do
|
147
|
+
proc { deserialized_serializer.serialized_args! }.must_raise RuntimeError
|
148
|
+
serialized_serializer.serialized_args! # Must not raise
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'performs_serialization!' do
|
152
|
+
deserialized_serializer.perform_serialization!
|
153
|
+
deserialized_serializer.serialized_args!.must_equal args
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'performs_deserialization!' do
|
157
|
+
serialized_serializer.perform_deserialization!
|
158
|
+
serialized_serializer.args.must_equal args
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe 'delayed plan' do
|
163
|
+
let(:args) { %w(arg1 arg2) }
|
164
|
+
let(:serializer) { Dynflow::Serializers::Noop.new(nil, args) }
|
165
|
+
let(:delayed_plan) do
|
166
|
+
Dynflow::DelayedPlan.new(Dynflow::World.allocate, 'an uuid', nil, nil, serializer)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "allows access to serializer's args" do
|
170
|
+
serializer.args.must_be :nil?
|
171
|
+
delayed_plan.args.must_equal args
|
172
|
+
serializer.args.must_equal args
|
173
|
+
end
|
137
174
|
end
|
138
175
|
end
|
139
176
|
end
|
data/test/persistence_test.rb
CHANGED
@@ -6,21 +6,31 @@ module Dynflow
|
|
6
6
|
describe 'persistence adapters' do
|
7
7
|
|
8
8
|
let :execution_plans_data do
|
9
|
-
[{ id: 'plan1', :label => 'test1', state: 'paused' },
|
10
|
-
{ id: 'plan2', :label => 'test2', state: 'stopped' },
|
11
|
-
{ id: 'plan3', :label => 'test3', state: 'paused' },
|
12
|
-
{ id: 'plan4', :label => 'test4', state: 'paused' }]
|
9
|
+
[{ id: 'plan1', :label => 'test1', root_plan_step_id: 1, class: 'Dynflow::ExecutionPlan', state: 'paused' },
|
10
|
+
{ id: 'plan2', :label => 'test2', root_plan_step_id: 1, class: 'Dynflow::ExecutionPlan', state: 'stopped' },
|
11
|
+
{ id: 'plan3', :label => 'test3', root_plan_step_id: 1, class: 'Dynflow::ExecutionPlan', state: 'paused' },
|
12
|
+
{ id: 'plan4', :label => 'test4', root_plan_step_id: 1, class: 'Dynflow::ExecutionPlan', state: 'paused' }]
|
13
13
|
end
|
14
14
|
|
15
15
|
let :action_data do
|
16
|
-
{
|
16
|
+
{
|
17
|
+
id: 1,
|
18
|
+
caller_execution_plan_id: nil,
|
19
|
+
caller_action_id: nil,
|
20
|
+
class: 'Dynflow::Action',
|
21
|
+
input: {key: 'value'},
|
22
|
+
output: {something: 'else'},
|
23
|
+
plan_step_id: 1,
|
24
|
+
run_step_id: 2,
|
25
|
+
finalize_step_id: 3
|
26
|
+
}
|
17
27
|
end
|
18
28
|
|
19
29
|
let :step_data do
|
20
30
|
{ id: 1,
|
21
31
|
state: 'success',
|
22
|
-
started_at:
|
23
|
-
ended_at:
|
32
|
+
started_at: Time.now.utc - 60,
|
33
|
+
ended_at: Time.now.utc - 30,
|
24
34
|
real_time: 1.1,
|
25
35
|
execution_time: 0.1,
|
26
36
|
action_id: 1,
|
@@ -30,13 +40,15 @@ module Dynflow
|
|
30
40
|
|
31
41
|
def prepare_plans
|
32
42
|
execution_plans_data.map do |h|
|
33
|
-
h.merge result: nil, started_at:
|
43
|
+
h.merge result: nil, started_at: Time.now.utc - 20, ended_at: Time.now.utc - 10,
|
34
44
|
real_time: 0.0, execution_time: 0.0
|
35
|
-
end.tap do |plans|
|
36
|
-
plans.each { |plan| adapter.save_execution_plan(plan[:id], plan) }
|
37
45
|
end
|
38
46
|
end
|
39
47
|
|
48
|
+
def prepare_and_save_plans
|
49
|
+
prepare_plans.each { |plan| adapter.save_execution_plan(plan[:id], plan) }
|
50
|
+
end
|
51
|
+
|
40
52
|
def format_time(time)
|
41
53
|
time.strftime('%Y-%m-%d %H:%M:%S')
|
42
54
|
end
|
@@ -46,18 +58,38 @@ module Dynflow
|
|
46
58
|
end
|
47
59
|
|
48
60
|
def prepare_step(plan)
|
49
|
-
|
61
|
+
step = step_data.dup
|
62
|
+
step[:execution_plan_uuid] = plan
|
63
|
+
step
|
64
|
+
end
|
65
|
+
|
66
|
+
def prepare_and_save_step(plan)
|
67
|
+
step = prepare_step(plan)
|
68
|
+
adapter.save_step(plan, step[:id], step)
|
50
69
|
end
|
51
70
|
|
52
71
|
def prepare_plans_with_actions
|
53
|
-
|
72
|
+
prepare_and_save_plans.each do |plan|
|
54
73
|
prepare_action(plan[:id])
|
55
74
|
end
|
56
75
|
end
|
57
76
|
|
58
77
|
def prepare_plans_with_steps
|
59
78
|
prepare_plans_with_actions.map do |plan|
|
60
|
-
|
79
|
+
prepare_and_save_step(plan[:id])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def assert_equal_attributes!(original, loaded)
|
84
|
+
original.each do |key, value|
|
85
|
+
loaded_value = loaded[key.to_s]
|
86
|
+
if value.is_a?(Time)
|
87
|
+
loaded_value.inspect.must_equal value.inspect
|
88
|
+
elsif value.is_a?(Hash)
|
89
|
+
assert_equal_attributes!(value, loaded_value)
|
90
|
+
else
|
91
|
+
loaded[key.to_s].must_equal value
|
92
|
+
end
|
61
93
|
end
|
62
94
|
end
|
63
95
|
|
@@ -69,7 +101,7 @@ module Dynflow
|
|
69
101
|
end
|
70
102
|
describe '#find_execution_plans' do
|
71
103
|
it 'supports pagination' do
|
72
|
-
|
104
|
+
prepare_and_save_plans
|
73
105
|
if adapter.pagination?
|
74
106
|
loaded_plans = adapter.find_execution_plans(page: 0, per_page: 1)
|
75
107
|
loaded_plans.map { |h| h[:id] }.must_equal ['plan1']
|
@@ -80,7 +112,7 @@ module Dynflow
|
|
80
112
|
end
|
81
113
|
|
82
114
|
it 'supports ordering' do
|
83
|
-
|
115
|
+
prepare_and_save_plans
|
84
116
|
if adapter.ordering_by.include?('state')
|
85
117
|
loaded_plans = adapter.find_execution_plans(order_by: 'state')
|
86
118
|
loaded_plans.map { |h| h[:id] }.must_equal %w(plan1 plan3 plan4 plan2)
|
@@ -91,7 +123,7 @@ module Dynflow
|
|
91
123
|
end
|
92
124
|
|
93
125
|
it 'supports filtering' do
|
94
|
-
|
126
|
+
prepare_and_save_plans
|
95
127
|
if adapter.ordering_by.include?('state')
|
96
128
|
loaded_plans = adapter.find_execution_plans(filters: { label: ['test1'] })
|
97
129
|
loaded_plans.map { |h| h[:id] }.must_equal ['plan1']
|
@@ -130,7 +162,7 @@ module Dynflow
|
|
130
162
|
end
|
131
163
|
|
132
164
|
it 'supports filtering' do
|
133
|
-
|
165
|
+
prepare_and_save_plans
|
134
166
|
if adapter.ordering_by.include?('state')
|
135
167
|
loaded_plans = adapter.find_execution_plan_counts(filters: { label: ['test1'] })
|
136
168
|
loaded_plans.must_equal 1
|
@@ -165,10 +197,12 @@ module Dynflow
|
|
165
197
|
describe '#load_execution_plan and #save_execution_plan' do
|
166
198
|
it 'serializes/deserializes the plan data' do
|
167
199
|
-> { adapter.load_execution_plan('plan1') }.must_raise KeyError
|
168
|
-
|
169
|
-
adapter.load_execution_plan('plan1')
|
170
|
-
|
171
|
-
|
200
|
+
plan = prepare_and_save_plans.first
|
201
|
+
loaded_plan = adapter.load_execution_plan('plan1')
|
202
|
+
loaded_plan[:id].must_equal 'plan1'
|
203
|
+
loaded_plan['id'].must_equal 'plan1'
|
204
|
+
|
205
|
+
assert_equal_attributes!(plan, loaded_plan)
|
172
206
|
|
173
207
|
adapter.save_execution_plan('plan1', nil)
|
174
208
|
-> { adapter.load_execution_plan('plan1') }.must_raise KeyError
|
@@ -214,37 +248,60 @@ module Dynflow
|
|
214
248
|
|
215
249
|
describe '#load_action and #save_action' do
|
216
250
|
it 'serializes/deserializes the action data' do
|
217
|
-
|
251
|
+
prepare_and_save_plans
|
252
|
+
action = action_data.dup
|
218
253
|
action_id = action_data[:id]
|
219
254
|
-> { adapter.load_action('plan1', action_id) }.must_raise KeyError
|
220
255
|
|
221
256
|
prepare_action('plan1')
|
222
257
|
loaded_action = adapter.load_action('plan1', action_id)
|
223
258
|
loaded_action[:id].must_equal action_id
|
224
|
-
|
259
|
+
|
260
|
+
assert_equal_attributes!(action, loaded_action)
|
225
261
|
|
226
262
|
adapter.save_action('plan1', action_id, nil)
|
227
263
|
-> { adapter.load_action('plan1', action_id) }.must_raise KeyError
|
228
264
|
|
229
265
|
adapter.save_execution_plan('plan1', nil)
|
230
266
|
end
|
267
|
+
|
268
|
+
it 'allow to retrieve specific attributes using #load_actions_attributes' do
|
269
|
+
prepare_and_save_plans
|
270
|
+
prepare_action('plan1')
|
271
|
+
loaded_data = adapter.load_actions_attributes('plan1', [:id, :run_step_id]).first
|
272
|
+
loaded_data.keys.count.must_equal 2
|
273
|
+
loaded_data[:id].must_equal action_data[:id]
|
274
|
+
loaded_data[:run_step_id].must_equal action_data[:run_step_id]
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'allows to load actions in bulk using #load_actions' do
|
278
|
+
prepare_and_save_plans
|
279
|
+
prepare_action('plan1')
|
280
|
+
action = action_data.dup
|
281
|
+
loaded_actions = adapter.load_actions('plan1', [1])
|
282
|
+
loaded_actions.count.must_equal 1
|
283
|
+
loaded_action = loaded_actions.first
|
284
|
+
|
285
|
+
assert_equal_attributes!(action, loaded_action)
|
286
|
+
end
|
231
287
|
end
|
232
288
|
|
233
289
|
describe '#load_step and #save_step' do
|
234
290
|
it 'serializes/deserializes the step data' do
|
235
291
|
prepare_plans_with_actions
|
236
292
|
step_id = step_data[:id]
|
237
|
-
|
293
|
+
prepare_and_save_step('plan1')
|
238
294
|
loaded_step = adapter.load_step('plan1', step_id)
|
239
295
|
loaded_step[:id].must_equal step_id
|
240
|
-
|
296
|
+
|
297
|
+
assert_equal_attributes!(step_data, loaded_step)
|
241
298
|
end
|
242
299
|
end
|
243
300
|
|
244
301
|
describe '#find_past_delayed_plans' do
|
245
302
|
it 'finds plans with start_before in past' do
|
246
|
-
start_time = Time.now
|
247
|
-
|
303
|
+
start_time = Time.now.utc
|
304
|
+
prepare_and_save_plans
|
248
305
|
adapter.save_delayed_plan('plan1', :execution_plan_uuid => 'plan1', :start_at => format_time(start_time + 60),
|
249
306
|
:start_before => format_time(start_time - 60))
|
250
307
|
adapter.save_delayed_plan('plan2', :execution_plan_uuid => 'plan2', :start_at => format_time(start_time - 60))
|
@@ -264,15 +321,16 @@ module Dynflow
|
|
264
321
|
it_acts_as_persistence_adapter
|
265
322
|
|
266
323
|
it 'allows inspecting the persisted content' do
|
267
|
-
plans =
|
324
|
+
plans = prepare_and_save_plans
|
268
325
|
|
269
326
|
plans.each do |original|
|
270
327
|
stored = adapter.to_hash.fetch(:execution_plans).find { |ep| ep[:uuid].strip == original[:id] }
|
271
|
-
stored.each { |k, v| stored[k] = v.to_s if v.is_a? Time }
|
272
328
|
adapter.class::META_DATA.fetch(:execution_plan).each do |name|
|
273
329
|
value = original.fetch(name.to_sym)
|
274
330
|
if value.nil?
|
275
331
|
stored.fetch(name.to_sym).must_be_nil
|
332
|
+
elsif value.is_a?(Time)
|
333
|
+
stored.fetch(name.to_sym).inspect.must_equal value.inspect
|
276
334
|
else
|
277
335
|
stored.fetch(name.to_sym).must_equal value
|
278
336
|
end
|
@@ -296,6 +354,63 @@ module Dynflow
|
|
296
354
|
assert_equal [], adapter.pull_envelopes(executor_world_id)
|
297
355
|
end
|
298
356
|
|
357
|
+
it 'supports reading data saved prior to normalization' do
|
358
|
+
db = adapter.send(:db)
|
359
|
+
# Prepare records for saving
|
360
|
+
plan = prepare_plans.first
|
361
|
+
step_data = prepare_step(plan[:id])
|
362
|
+
|
363
|
+
# We used to store times as strings
|
364
|
+
plan[:started_at] = format_time plan[:started_at]
|
365
|
+
plan[:ended_at] = format_time plan[:ended_at]
|
366
|
+
step_data[:started_at] = format_time step_data[:started_at]
|
367
|
+
step_data[:ended_at] = format_time step_data[:ended_at]
|
368
|
+
|
369
|
+
plan_record = adapter.send(:prepare_record, :execution_plan, plan.merge(:uuid => plan[:id]))
|
370
|
+
action_record = adapter.send(:prepare_record, :action, action_data.dup)
|
371
|
+
step_record = adapter.send(:prepare_record, :step, step_data)
|
372
|
+
|
373
|
+
# Insert the records
|
374
|
+
db[:dynflow_execution_plans].insert plan_record.merge(:uuid => plan[:id])
|
375
|
+
db[:dynflow_actions].insert action_record.merge(:execution_plan_uuid => plan[:id], :id => action_data[:id])
|
376
|
+
db[:dynflow_steps].insert step_record.merge(:execution_plan_uuid => plan[:id], :id => step_data[:id])
|
377
|
+
|
378
|
+
# Load the saved records
|
379
|
+
loaded_plan = adapter.load_execution_plan(plan[:id])
|
380
|
+
loaded_action = adapter.load_action(plan[:id], action_data[:id])
|
381
|
+
loaded_step = adapter.load_step(plan[:id], step_data[:id])
|
382
|
+
|
383
|
+
# Test
|
384
|
+
assert_equal_attributes!(plan, loaded_plan)
|
385
|
+
assert_equal_attributes!(action_data, loaded_action)
|
386
|
+
assert_equal_attributes!(step_data, loaded_step)
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'support updating data saved prior to normalization' do
|
390
|
+
db = adapter.send(:db)
|
391
|
+
plan = prepare_plans.first
|
392
|
+
plan_data = plan.dup
|
393
|
+
plan[:started_at] = format_time plan[:started_at]
|
394
|
+
plan[:ended_at] = format_time plan[:ended_at]
|
395
|
+
plan_record = adapter.send(:prepare_record, :execution_plan, plan.merge(:uuid => plan[:id]))
|
396
|
+
|
397
|
+
# Save the plan the old way
|
398
|
+
db[:dynflow_execution_plans].insert plan_record.merge(:uuid => plan[:id])
|
399
|
+
|
400
|
+
# Update and save the plan
|
401
|
+
plan_data[:state] = 'stopped'
|
402
|
+
plan_data[:result] = 'success'
|
403
|
+
adapter.save_execution_plan(plan[:id], plan_data)
|
404
|
+
|
405
|
+
# Check the plan has the changed columns populated
|
406
|
+
raw_plan = db[:dynflow_execution_plans].where(:uuid => 'plan1').first
|
407
|
+
raw_plan[:state].must_equal 'stopped'
|
408
|
+
raw_plan[:result].must_equal 'success'
|
409
|
+
|
410
|
+
# Load the plan and assert it doesn't read attributes from data
|
411
|
+
loaded_plan = adapter.load_execution_plan(plan[:id])
|
412
|
+
assert_equal_attributes!(plan_data, loaded_plan)
|
413
|
+
end
|
299
414
|
end
|
300
415
|
end
|
301
416
|
end
|