dynflow 1.2.1 → 1.2.2
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/.rubocop.yml +3 -0
- data/lib/dynflow/action/with_sub_plans.rb +5 -5
- data/lib/dynflow/active_job/queue_adapter.rb +11 -7
- data/lib/dynflow/director.rb +41 -14
- data/lib/dynflow/director/execution_plan_manager.rb +6 -2
- data/lib/dynflow/execution_plan.rb +8 -15
- data/lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb +4 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/{011_add_uuid_column.rb → 018_add_uuid_column.rb} +2 -2
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world.rb +7 -10
- data/lib/dynflow/world/invalidation.rb +3 -2
- data/test/abnormal_states_recovery_test.rb +4 -9
- data/test/activejob_adapter_test.rb +8 -1
- data/test/execution_plan_test.rb +13 -0
- data/test/executor_test.rb +1 -0
- data/test/rescue_test.rb +109 -92
- data/test/test_helper.rb +3 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddba15f840b26c5148fcebf8a0b69ff22abc0bba1fe70d2fa02e32722003011b
|
4
|
+
data.tar.gz: 4c7c6a7c3d23e1e0684ef61fa44ccf1cd44ff76cfba9c0e8e3b0587b9674ffeb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5b836d767ef81131f217d82467d96410c63140bb38e247489b33b9386df2ddb30aff56da3b42bff216fd6a419f714176d0e111415e6793c3c534bdbfcbd6938
|
7
|
+
data.tar.gz: 01a74517eb6500b5e986759d017f2886c1a5032d81e026d1e41450033935dfab9c342bf4ced411da56b2cf3daefd79a82a1137d97030567bbcbe517a4e9599cb
|
data/.rubocop.yml
CHANGED
@@ -76,16 +76,16 @@ module Dynflow
|
|
76
76
|
end
|
77
77
|
|
78
78
|
# Helper for creating sub plans
|
79
|
-
def trigger(*args)
|
79
|
+
def trigger(action_class, *args)
|
80
80
|
if uses_concurrency_control
|
81
|
-
trigger_with_concurrency_control(*args)
|
81
|
+
trigger_with_concurrency_control(action_class, *args)
|
82
82
|
else
|
83
|
-
world.trigger { world.
|
83
|
+
world.trigger { world.plan_with_options(action_class: action_class, args: args, caller_action: self) }
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
-
def trigger_with_concurrency_control(*args)
|
88
|
-
record = world.
|
87
|
+
def trigger_with_concurrency_control(action_class, *args)
|
88
|
+
record = world.plan_with_options(action_class: action_class, args: args, caller_action: self)
|
89
89
|
records = [[record.id], []]
|
90
90
|
records.reverse! unless record.state == :planned
|
91
91
|
@world.throttle_limiter.handle_plans!(execution_plan_id, *records).first
|
@@ -3,15 +3,19 @@ module Dynflow
|
|
3
3
|
module QueueAdapters
|
4
4
|
module QueueMethods
|
5
5
|
def enqueue(job)
|
6
|
-
::Rails.application.dynflow.world.trigger
|
7
|
-
job.provider_job_id =
|
6
|
+
::Rails.application.dynflow.world.trigger do |world|
|
7
|
+
job.provider_job_id = job.job_id
|
8
|
+
world.plan_with_options(id: job.provider_job_id, action_class: JobWrapper, args: [job.serialize])
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
12
|
def enqueue_at(job, timestamp)
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
job.provider_job_id = job.job_id
|
14
|
+
::Rails.application.dynflow.world
|
15
|
+
.delay_with_options(id: job.provider_job_id,
|
16
|
+
action_class: JobWrapper,
|
17
|
+
delay_options: { :start_at => Time.at(timestamp) },
|
18
|
+
args: [job.serialize])
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
@@ -33,13 +37,13 @@ module Dynflow
|
|
33
37
|
|
34
38
|
def plan(attributes)
|
35
39
|
input[:job_class] = attributes['job_class']
|
36
|
-
input[:job_arguments] = attributes['arguments']
|
37
40
|
input[:queue] = attributes['queue_name']
|
41
|
+
input[:job_data] = attributes
|
38
42
|
plan_self
|
39
43
|
end
|
40
44
|
|
41
45
|
def run
|
42
|
-
|
46
|
+
::ActiveJob::Base.execute(input[:job_data])
|
43
47
|
end
|
44
48
|
|
45
49
|
def label
|
data/lib/dynflow/director.rb
CHANGED
@@ -119,7 +119,9 @@ module Dynflow
|
|
119
119
|
def work_failed(work)
|
120
120
|
if (manager = @execution_plan_managers[work.execution_plan_id])
|
121
121
|
manager.terminate
|
122
|
-
|
122
|
+
# Don't try to store when the execution plan went missing
|
123
|
+
plan_missing = @world.persistence.find_execution_plans(:filters => { uuid: work.execution_plan_id }).empty?
|
124
|
+
finish_manager(manager, store: !plan_missing)
|
123
125
|
end
|
124
126
|
end
|
125
127
|
|
@@ -144,24 +146,26 @@ module Dynflow
|
|
144
146
|
def unless_done(manager, work_items)
|
145
147
|
return [] unless manager
|
146
148
|
if manager.done?
|
147
|
-
finish_manager(manager)
|
148
|
-
return []
|
149
|
+
try_to_rescue(manager) || finish_manager(manager)
|
149
150
|
else
|
150
151
|
return work_items
|
151
152
|
end
|
152
153
|
end
|
153
154
|
|
154
|
-
def
|
155
|
+
def try_to_rescue(manager)
|
156
|
+
rescue!(manager) if rescue?(manager)
|
157
|
+
end
|
158
|
+
|
159
|
+
def finish_manager(manager, store: true)
|
160
|
+
update_execution_plan_state(manager) if store
|
161
|
+
return []
|
162
|
+
ensure
|
155
163
|
@execution_plan_managers.delete(manager.execution_plan.id)
|
156
|
-
|
157
|
-
rescue!(manager)
|
158
|
-
else
|
159
|
-
set_future(manager)
|
160
|
-
end
|
164
|
+
set_future(manager)
|
161
165
|
end
|
162
166
|
|
163
167
|
def rescue?(manager)
|
164
|
-
if @world.terminating? || !(@world.auto_rescue && manager.execution_plan.
|
168
|
+
if @world.terminating? || !(@world.auto_rescue && manager.execution_plan.error?)
|
165
169
|
false
|
166
170
|
elsif !@rescued_steps.key?(manager.execution_plan.id)
|
167
171
|
# we have not rescued this plan yet
|
@@ -179,11 +183,12 @@ module Dynflow
|
|
179
183
|
# to put this logic of making sure we don't run rescues in endless loop
|
180
184
|
@rescued_steps[manager.execution_plan.id] ||= Set.new
|
181
185
|
@rescued_steps[manager.execution_plan.id].merge(manager.execution_plan.failed_steps.map(&:id))
|
182
|
-
|
183
|
-
if
|
184
|
-
|
186
|
+
new_state = manager.execution_plan.prepare_for_rescue
|
187
|
+
if new_state == :running
|
188
|
+
return manager.restart
|
185
189
|
else
|
186
|
-
|
190
|
+
manager.execution_plan.state = new_state
|
191
|
+
return false
|
187
192
|
end
|
188
193
|
end
|
189
194
|
|
@@ -207,6 +212,28 @@ module Dynflow
|
|
207
212
|
nil
|
208
213
|
end
|
209
214
|
|
215
|
+
def update_execution_plan_state(manager)
|
216
|
+
execution_plan = manager.execution_plan
|
217
|
+
case execution_plan.state
|
218
|
+
when :running
|
219
|
+
if execution_plan.error?
|
220
|
+
execution_plan.execution_history.add('pause execution', @world.id)
|
221
|
+
execution_plan.update_state(:paused)
|
222
|
+
elsif manager.done?
|
223
|
+
execution_plan.execution_history.add('finish execution', @world.id)
|
224
|
+
execution_plan.update_state(:stopped)
|
225
|
+
end
|
226
|
+
# If the state is marked as running without errors but manager is not done,
|
227
|
+
# we let the invalidation procedure to handle re-execution on other executor
|
228
|
+
when :paused
|
229
|
+
execution_plan.execution_history.add('pause execution', @world.id)
|
230
|
+
execution_plan.save
|
231
|
+
when :stopped
|
232
|
+
execution_plan.execution_history.add('finish execution', @world.id)
|
233
|
+
execution_plan.save
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
210
237
|
def set_future(manager)
|
211
238
|
@rescued_steps.delete(manager.execution_plan.id)
|
212
239
|
manager.future.fulfill manager.execution_plan
|
@@ -24,6 +24,12 @@ module Dynflow
|
|
24
24
|
start_run or start_finalize or finish
|
25
25
|
end
|
26
26
|
|
27
|
+
def restart
|
28
|
+
@run_manager = nil
|
29
|
+
@finalize_manager = nil
|
30
|
+
start
|
31
|
+
end
|
32
|
+
|
27
33
|
def prepare_next_step(step)
|
28
34
|
StepWorkItem.new(execution_plan.id, step, step.queue).tap do |work|
|
29
35
|
@running_steps_manager.add(step, work)
|
@@ -97,8 +103,6 @@ module Dynflow
|
|
97
103
|
end
|
98
104
|
|
99
105
|
def finish
|
100
|
-
execution_plan.execution_history.add('finish execution', @world.id)
|
101
|
-
@execution_plan.update_state(execution_plan.error? ? :paused : :stopped)
|
102
106
|
return no_work
|
103
107
|
end
|
104
108
|
|
@@ -69,7 +69,7 @@ module Dynflow
|
|
69
69
|
|
70
70
|
# all params with default values are part of *private* api
|
71
71
|
def initialize(world,
|
72
|
-
id =
|
72
|
+
id = nil,
|
73
73
|
label = nil,
|
74
74
|
state = :pending,
|
75
75
|
root_plan_step = nil,
|
@@ -81,7 +81,7 @@ module Dynflow
|
|
81
81
|
execution_time = nil,
|
82
82
|
real_time = 0.0,
|
83
83
|
execution_history = ExecutionHistory.new)
|
84
|
-
|
84
|
+
id ||= SecureRandom.uuid
|
85
85
|
@id = Type! id, String
|
86
86
|
@world = Type! world, World
|
87
87
|
@label = Type! label, String, NilClass
|
@@ -199,16 +199,17 @@ module Dynflow
|
|
199
199
|
persistence.find_execution_plan_counts(filters: { 'caller_execution_plan_id' => self.id })
|
200
200
|
end
|
201
201
|
|
202
|
-
def
|
202
|
+
def prepare_for_rescue
|
203
203
|
case rescue_strategy
|
204
204
|
when Action::Rescue::Pause
|
205
|
-
|
205
|
+
:paused
|
206
206
|
when Action::Rescue::Fail
|
207
|
-
|
208
|
-
nil
|
207
|
+
:stopped
|
209
208
|
when Action::Rescue::Skip
|
210
209
|
failed_steps.each { |step| self.skip(step) }
|
211
|
-
|
210
|
+
:running
|
211
|
+
else
|
212
|
+
:paused
|
212
213
|
end
|
213
214
|
end
|
214
215
|
|
@@ -232,14 +233,6 @@ module Dynflow
|
|
232
233
|
self.steps.values.find_all {|step| states.include?(step.state) }
|
233
234
|
end
|
234
235
|
|
235
|
-
def rescue_from_error
|
236
|
-
if rescue_plan_id = self.rescue_plan_id
|
237
|
-
@world.execute(rescue_plan_id)
|
238
|
-
else
|
239
|
-
raise Errors::RescueError, 'Unable to rescue from the error'
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
236
|
def generate_action_id
|
244
237
|
@last_action_id ||= 0
|
245
238
|
@last_action_id += 1
|
@@ -38,7 +38,7 @@ end
|
|
38
38
|
|
39
39
|
Sequel.migration do
|
40
40
|
up do
|
41
|
-
if database_type
|
41
|
+
if database_type.to_s.include?('postgres')
|
42
42
|
with_foreign_key_recreation do
|
43
43
|
to_uuid :dynflow_execution_plans, :uuid
|
44
44
|
to_uuid :dynflow_actions, :execution_plan_uuid
|
@@ -49,7 +49,7 @@ Sequel.migration do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
down do
|
52
|
-
if database_type
|
52
|
+
if database_type.to_s.include?('postgres')
|
53
53
|
with_foreign_key_recreation do
|
54
54
|
from_uuid :dynflow_execution_plans, :uuid
|
55
55
|
from_uuid :dynflow_actions, :execution_plan_uuid
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/world.rb
CHANGED
@@ -183,26 +183,23 @@ module Dynflow
|
|
183
183
|
end
|
184
184
|
end
|
185
185
|
|
186
|
-
def delay(*args)
|
187
|
-
|
186
|
+
def delay(action_class, delay_options, *args)
|
187
|
+
delay_with_options(action_class: action_class, args: args, delay_options: delay_options)
|
188
188
|
end
|
189
189
|
|
190
|
-
def
|
190
|
+
def delay_with_options(action_class:, args:, delay_options:, id: nil, caller_action: nil)
|
191
191
|
raise 'No action_class given' if action_class.nil?
|
192
|
-
execution_plan = ExecutionPlan.new(self)
|
192
|
+
execution_plan = ExecutionPlan.new(self, id)
|
193
193
|
execution_plan.delay(caller_action, action_class, delay_options, *args)
|
194
194
|
Scheduled[execution_plan.id]
|
195
195
|
end
|
196
196
|
|
197
197
|
def plan(action_class, *args)
|
198
|
-
|
199
|
-
execution_plan.prepare(action_class)
|
200
|
-
execution_plan.plan(*args)
|
201
|
-
end
|
198
|
+
plan_with_options(action_class: action_class, args: args)
|
202
199
|
end
|
203
200
|
|
204
|
-
def
|
205
|
-
ExecutionPlan.new(self).tap do |execution_plan|
|
201
|
+
def plan_with_options(action_class:, args:, id: nil, caller_action: nil)
|
202
|
+
ExecutionPlan.new(self, id).tap do |execution_plan|
|
206
203
|
execution_plan.prepare(action_class, caller_action: caller_action)
|
207
204
|
execution_plan.plan(*args)
|
208
205
|
end
|
@@ -9,6 +9,7 @@ module Dynflow
|
|
9
9
|
# @return [void]
|
10
10
|
def invalidate(world)
|
11
11
|
Type! world, Coordinator::ClientWorld, Coordinator::ExecutorWorld
|
12
|
+
|
12
13
|
coordinator.acquire(Coordinator::WorldInvalidationLock.new(self, world)) do
|
13
14
|
if world.is_a? Coordinator::ExecutorWorld
|
14
15
|
old_execution_locks = coordinator.find_locks(class: Coordinator::ExecutionLock.name,
|
@@ -47,8 +48,8 @@ module Dynflow
|
|
47
48
|
coordinator.release(execution_lock)
|
48
49
|
|
49
50
|
if plan.error?
|
50
|
-
|
51
|
-
execute(
|
51
|
+
new_state = plan.prepare_for_rescue
|
52
|
+
execute(plan.id) if new_state == :running
|
52
53
|
else
|
53
54
|
if coordinator.find_worlds(true).any? # Check if there are any executors
|
54
55
|
client_dispatcher.tell([:dispatch_request,
|
@@ -142,13 +142,7 @@ module Dynflow
|
|
142
142
|
describe 'auto execute' do
|
143
143
|
|
144
144
|
before do
|
145
|
-
client_world.persistence.
|
146
|
-
# make sure we don't handle plans from previous tests
|
147
|
-
# TODO: delete the plans instead, once we have
|
148
|
-
# https://github.com/Dynflow/dynflow/pull/141 merged
|
149
|
-
plan.set_state(:stopped, true)
|
150
|
-
plan.save
|
151
|
-
end
|
145
|
+
client_world.persistence.delete_execution_plans({})
|
152
146
|
end
|
153
147
|
|
154
148
|
it "prevents from running the auto-execution twice" do
|
@@ -198,7 +192,7 @@ module Dynflow
|
|
198
192
|
plan = client_world.persistence.load_execution_plan(triggered.id)
|
199
193
|
plan.state.must_equal :paused
|
200
194
|
expected_history = [['start execution', executor_world.id],
|
201
|
-
['
|
195
|
+
['pause execution', executor_world.id]]
|
202
196
|
plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
|
203
197
|
end
|
204
198
|
end
|
@@ -318,10 +312,11 @@ module Dynflow
|
|
318
312
|
plan
|
319
313
|
end
|
320
314
|
|
315
|
+
let(:sample_uuid) { '11111111-2222-3333-4444-555555555555' }
|
321
316
|
let(:valid_plan) { plan_in_state :running }
|
322
317
|
let(:invalid_plan) { plan_in_state :stopped }
|
323
318
|
let(:valid_lock) { Coordinator::SingletonActionLock.new('MyClass1', valid_plan.id) }
|
324
|
-
let(:invalid_lock) { Coordinator::SingletonActionLock.new('MyClass2',
|
319
|
+
let(:invalid_lock) { Coordinator::SingletonActionLock.new('MyClass2', sample_uuid) }
|
325
320
|
let(:invalid_lock2) { Coordinator::SingletonActionLock.new('MyClass3', invalid_plan.id) }
|
326
321
|
|
327
322
|
it 'unlocks orphaned singleton action locks' do
|
@@ -8,10 +8,13 @@ module Dynflow
|
|
8
8
|
|
9
9
|
def perform(msg)
|
10
10
|
puts "This job says #{msg}"
|
11
|
+
puts "provider_job_id is #{provider_job_id}"
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
15
|
describe 'running jobs' do
|
16
|
+
include TestHelpers
|
17
|
+
|
15
18
|
let :world do
|
16
19
|
WorldFactory.create_world
|
17
20
|
end
|
@@ -48,9 +51,13 @@ module Dynflow
|
|
48
51
|
job = nil
|
49
52
|
out, = capture_subprocess_io do
|
50
53
|
job = SampleJob.perform_later 'hello'
|
54
|
+
wait_for do
|
55
|
+
plan = world.persistence.load_execution_plan(job.provider_job_id)
|
56
|
+
plan.state == :stopped
|
57
|
+
end
|
51
58
|
end
|
52
|
-
assert world.persistence.load_execution_plan(job.provider_job_id)
|
53
59
|
assert_match(/Enqueued Dynflow::SampleJob/, out)
|
60
|
+
assert_match(/provider_job_id is #{job.provider_job_id}/, out)
|
54
61
|
end
|
55
62
|
|
56
63
|
it 'schedules job in the future' do
|
data/test/execution_plan_test.rb
CHANGED
@@ -191,6 +191,19 @@ module Dynflow
|
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
194
|
+
describe 'custom plan id' do
|
195
|
+
let(:sample_uuid) { '60366107-9910-4815-a6c6-bc45ee2ea2b8' }
|
196
|
+
let :execution_plan do
|
197
|
+
world.plan_with_options(action_class: Support::CodeWorkflowExample::IncomingIssues,
|
198
|
+
args: [issues_data],
|
199
|
+
id: sample_uuid)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'allows setting custom id for the execution plan' do
|
203
|
+
execution_plan.id.must_equal sample_uuid
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
194
207
|
describe 'planning algorithm' do
|
195
208
|
|
196
209
|
describe 'single dependencies' do
|
data/test/executor_test.rb
CHANGED
@@ -138,6 +138,7 @@ module Dynflow
|
|
138
138
|
wait_for('execution plan removed from executor') do
|
139
139
|
!director.current_execution_plan_ids.include?(execution_plan.id)
|
140
140
|
end
|
141
|
+
world.persistence.find_execution_plans(filters: { uuid: [execution_plan.id] }).must_be :empty?
|
141
142
|
end
|
142
143
|
end
|
143
144
|
end
|
data/test/rescue_test.rb
CHANGED
@@ -15,125 +15,99 @@ module Dynflow
|
|
15
15
|
end
|
16
16
|
|
17
17
|
let :rescued_plan do
|
18
|
-
execution_plan.
|
18
|
+
world.persistence.load_execution_plan(execution_plan.id)
|
19
19
|
end
|
20
20
|
|
21
|
-
describe '
|
21
|
+
describe 'no auto rescue' do
|
22
|
+
describe 'of simple skippable action in run phase' do
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
let :execution_plan do
|
25
|
+
execute(Example::ActionWithSkip, 1, :error_on_run)
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
it 'suggests skipping the action' do
|
29
|
+
execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
rescued_plan.entry_action.output[:message].
|
35
|
-
must_equal "skipped because some error as you wish"
|
32
|
+
it "doesn't rescue" do
|
33
|
+
rescued_plan.state.must_equal :paused
|
34
|
+
end
|
36
35
|
end
|
37
|
-
end
|
38
36
|
|
39
|
-
|
37
|
+
describe 'of simple skippable action in finalize phase' do
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
let :execution_plan do
|
40
|
+
execute(Example::ActionWithSkip, 1, :error_on_finalize)
|
41
|
+
end
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
it 'suggests skipping the action' do
|
44
|
+
execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
|
45
|
+
end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
rescued_plan.entry_action.output[:message].must_equal "Been here"
|
47
|
+
it "doesn't rescue" do
|
48
|
+
rescued_plan.state.must_equal :paused
|
49
|
+
end
|
53
50
|
end
|
54
51
|
|
55
|
-
|
52
|
+
describe 'of complex action with skips in run phase' do
|
56
53
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
execute(Example::ComplexActionWithSkip, :error_on_run)
|
61
|
-
end
|
54
|
+
let :execution_plan do
|
55
|
+
execute(Example::ComplexActionWithSkip, :error_on_run)
|
56
|
+
end
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
it 'suggests skipping the action' do
|
59
|
+
execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
|
60
|
+
end
|
66
61
|
|
67
|
-
|
68
|
-
|
69
|
-
rescued_plan.result.must_equal :warning
|
70
|
-
skipped_action = rescued_plan.actions.find do |action|
|
71
|
-
action.run_step && action.run_step.state == :skipped
|
62
|
+
it "doesn't rescue" do
|
63
|
+
rescued_plan.state.must_equal :paused
|
72
64
|
end
|
73
|
-
skipped_action.output[:message].must_equal "skipped because some error as you wish"
|
74
65
|
end
|
75
66
|
|
76
|
-
|
77
|
-
|
78
|
-
describe 'of complex action with skips in finalize phase' do
|
67
|
+
describe 'of complex action with skips in finalize phase' do
|
79
68
|
|
80
|
-
|
81
|
-
|
82
|
-
|
69
|
+
let :execution_plan do
|
70
|
+
execute(Example::ComplexActionWithSkip, :error_on_finalize)
|
71
|
+
end
|
83
72
|
|
84
|
-
|
85
|
-
|
86
|
-
|
73
|
+
it 'suggests skipping the action' do
|
74
|
+
execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
|
75
|
+
end
|
87
76
|
|
88
|
-
|
89
|
-
|
90
|
-
rescued_plan = execution_plan.rescue_from_error.value
|
91
|
-
rescued_plan = rescued_plan.rescue_from_error.value
|
92
|
-
rescued_plan.state.must_equal :stopped
|
93
|
-
rescued_plan.result.must_equal :warning
|
94
|
-
skipped_action = rescued_plan.actions.find do |action|
|
95
|
-
action.steps.find { |step| step && step.state == :skipped }
|
77
|
+
it "doesn't rescue" do
|
78
|
+
rescued_plan.state.must_equal :paused
|
96
79
|
end
|
97
|
-
skipped_action.output[:message].must_equal "Been here"
|
98
80
|
end
|
99
81
|
|
100
|
-
|
101
|
-
|
102
|
-
describe 'of complex action without skips' do
|
82
|
+
describe 'of complex action without skips' do
|
103
83
|
|
104
|
-
|
105
|
-
|
106
|
-
|
84
|
+
let :execution_plan do
|
85
|
+
execute(Example::ComplexActionWithoutSkip, :error_on_run)
|
86
|
+
end
|
107
87
|
|
108
|
-
|
109
|
-
|
110
|
-
|
88
|
+
it 'suggests pausing the plan' do
|
89
|
+
execution_plan.rescue_strategy.must_equal Action::Rescue::Pause
|
90
|
+
end
|
111
91
|
|
112
|
-
|
113
|
-
|
92
|
+
it "doesn't rescue" do
|
93
|
+
rescued_plan.state.must_equal :paused
|
94
|
+
end
|
114
95
|
end
|
115
96
|
|
116
|
-
|
97
|
+
describe 'of complex action with fail' do
|
117
98
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
execute(Example::ComplexActionWithFail, :error_on_run)
|
122
|
-
end
|
99
|
+
let :execution_plan do
|
100
|
+
execute(Example::ComplexActionWithFail, :error_on_run)
|
101
|
+
end
|
123
102
|
|
124
|
-
|
125
|
-
|
126
|
-
|
103
|
+
it 'suggests failing the plan' do
|
104
|
+
execution_plan.rescue_strategy.must_equal Action::Rescue::Fail
|
105
|
+
end
|
127
106
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
execution_plan.result.must_equal :error
|
132
|
-
execution_plan.steps_in_state(:success).count.must_equal 6
|
133
|
-
execution_plan.steps_in_state(:pending).count.must_equal 6
|
134
|
-
execution_plan.steps_in_state(:error).count.must_equal 1
|
107
|
+
it "doesn't rescue" do
|
108
|
+
rescued_plan.state.must_equal :paused
|
109
|
+
end
|
135
110
|
end
|
136
|
-
|
137
111
|
end
|
138
112
|
|
139
113
|
describe 'auto rescue' do
|
@@ -144,8 +118,32 @@ module Dynflow
|
|
144
118
|
end
|
145
119
|
end
|
146
120
|
|
147
|
-
describe 'of
|
121
|
+
describe 'of simple skippable action in run phase' do
|
122
|
+
let :execution_plan do
|
123
|
+
execute(Example::ActionWithSkip, 1, :error_on_run)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'skips the action and continues' do
|
127
|
+
rescued_plan.state.must_equal :stopped
|
128
|
+
rescued_plan.result.must_equal :warning
|
129
|
+
rescued_plan.entry_action.output[:message].
|
130
|
+
must_equal "skipped because some error as you wish"
|
131
|
+
end
|
132
|
+
end
|
148
133
|
|
134
|
+
describe 'of simple skippable action in finalize phase' do
|
135
|
+
let :execution_plan do
|
136
|
+
execute(Example::ActionWithSkip, 1, :error_on_finalize)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'skips the action and continues' do
|
140
|
+
rescued_plan.state.must_equal :stopped
|
141
|
+
rescued_plan.result.must_equal :warning
|
142
|
+
rescued_plan.entry_action.output[:message].must_equal "Been here"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe 'of plan with skips' do
|
149
147
|
let :execution_plan do
|
150
148
|
execute(Example::ComplexActionWithSkip, :error_on_run)
|
151
149
|
end
|
@@ -153,12 +151,29 @@ module Dynflow
|
|
153
151
|
it 'skips the action and continues automatically' do
|
154
152
|
execution_plan.state.must_equal :stopped
|
155
153
|
execution_plan.result.must_equal :warning
|
154
|
+
skipped_action = rescued_plan.actions.find do |action|
|
155
|
+
action.run_step && action.run_step.state == :skipped
|
156
|
+
end
|
157
|
+
skipped_action.output[:message].must_equal "skipped because some error as you wish"
|
156
158
|
end
|
159
|
+
end
|
157
160
|
|
161
|
+
describe 'of complex action with skips in finalize phase' do
|
162
|
+
let :execution_plan do
|
163
|
+
execute(Example::ComplexActionWithSkip, :error_on_finalize)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'skips the action and continues' do
|
167
|
+
rescued_plan.state.must_equal :stopped
|
168
|
+
rescued_plan.result.must_equal :warning
|
169
|
+
skipped_action = rescued_plan.actions.find do |action|
|
170
|
+
action.steps.find { |step| step && step.state == :skipped }
|
171
|
+
end
|
172
|
+
skipped_action.output[:message].must_equal "Been here"
|
173
|
+
end
|
158
174
|
end
|
159
175
|
|
160
176
|
describe 'of plan faild on auto-rescue' do
|
161
|
-
|
162
177
|
let :execution_plan do
|
163
178
|
execute(Example::ActionWithSkip, 1, :error_on_skip)
|
164
179
|
end
|
@@ -167,11 +182,9 @@ module Dynflow
|
|
167
182
|
execution_plan.state.must_equal :paused
|
168
183
|
execution_plan.result.must_equal :error
|
169
184
|
end
|
170
|
-
|
171
185
|
end
|
172
186
|
|
173
187
|
describe 'of plan without skips' do
|
174
|
-
|
175
188
|
let :execution_plan do
|
176
189
|
execute(Example::ComplexActionWithoutSkip, :error_on_run)
|
177
190
|
end
|
@@ -179,12 +192,13 @@ module Dynflow
|
|
179
192
|
it 'skips the action and continues automatically' do
|
180
193
|
execution_plan.state.must_equal :paused
|
181
194
|
execution_plan.result.must_equal :error
|
195
|
+
expected_history = [['start execution', world.id],
|
196
|
+
['pause execution', world.id]]
|
197
|
+
execution_plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
|
182
198
|
end
|
183
|
-
|
184
199
|
end
|
185
200
|
|
186
201
|
describe 'of plan with fail' do
|
187
|
-
|
188
202
|
let :execution_plan do
|
189
203
|
execute(Example::ComplexActionWithFail, :error_on_run)
|
190
204
|
end
|
@@ -195,6 +209,9 @@ module Dynflow
|
|
195
209
|
execution_plan.steps_in_state(:success).count.must_equal 6
|
196
210
|
execution_plan.steps_in_state(:pending).count.must_equal 6
|
197
211
|
execution_plan.steps_in_state(:error).count.must_equal 1
|
212
|
+
expected_history = [['start execution', world.id],
|
213
|
+
['finish execution', world.id]]
|
214
|
+
execution_plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
|
198
215
|
end
|
199
216
|
end
|
200
217
|
|
data/test/test_helper.rb
CHANGED
@@ -225,11 +225,11 @@ module TestHelpers
|
|
225
225
|
def assert_plan_reexecuted(plan)
|
226
226
|
assert_equal :stopped, plan.state
|
227
227
|
assert_equal :success, plan.result
|
228
|
-
assert_equal
|
229
|
-
['start execution',
|
228
|
+
assert_equal ['start execution',
|
230
229
|
'terminate execution',
|
231
230
|
'start execution',
|
232
|
-
'finish execution']
|
231
|
+
'finish execution'],
|
232
|
+
plan.execution_history.map(&:name)
|
233
233
|
refute_equal plan.execution_history.first.world_id, plan.execution_history.to_a.last.world_id
|
234
234
|
end
|
235
235
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Necas
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-02-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -481,13 +481,14 @@ files:
|
|
481
481
|
- lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb
|
482
482
|
- lib/dynflow/persistence_adapters/sequel_migrations/009_fix_mysql_data_length.rb
|
483
483
|
- lib/dynflow/persistence_adapters/sequel_migrations/010_add_execution_plans_label.rb
|
484
|
-
- lib/dynflow/persistence_adapters/sequel_migrations/
|
484
|
+
- lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb
|
485
485
|
- lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb
|
486
486
|
- lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb
|
487
487
|
- lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb
|
488
488
|
- lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb
|
489
489
|
- lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb
|
490
490
|
- lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb
|
491
|
+
- lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb
|
491
492
|
- lib/dynflow/rails.rb
|
492
493
|
- lib/dynflow/rails/configuration.rb
|
493
494
|
- lib/dynflow/rails/daemon.rb
|