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