dynflow 1.2.2 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/lib/dynflow/action.rb +9 -1
- data/lib/dynflow/delayed_plan.rb +2 -4
- data/lib/dynflow/director.rb +1 -9
- data/lib/dynflow/director/execution_plan_manager.rb +0 -1
- data/lib/dynflow/execution_plan.rb +24 -2
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +1 -1
- data/lib/dynflow/logger_adapters/simple.rb +1 -1
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world/invalidation.rb +2 -3
- data/test/action_test.rb +18 -0
- data/test/execution_plan_hooks_test.rb +66 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 403ea79d6ea134104cc54ccaca3edbdce414306b944ccd957f37421ef7070d65
|
4
|
+
data.tar.gz: 7361cc2b7e099d7b537738880dd4197034a7323b9e386c6e937518518931cadc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b8f9367989b27fc3a55294659441c3d0360ccc87d752cebd1f723d5a68400083ec0a0330a29811b868b87b459bf651a1c8fc3bce5c8399556cc26671c0c3d54
|
7
|
+
data.tar.gz: 95248248daebcf5d83b9be6bf3593a1ed55b41d5ef048180d30464091810d36361bdf3bdbc688f58625cdd6f5fdbb2a558fd1d8f56063b21cf65c8f03a8f8458
|
data/Gemfile
CHANGED
data/lib/dynflow/action.rb
CHANGED
@@ -571,7 +571,15 @@ module Dynflow
|
|
571
571
|
end
|
572
572
|
|
573
573
|
def root_action?
|
574
|
-
@
|
574
|
+
# in planning phase, the @triggered_action can be used to check whether the is root (the main action used
|
575
|
+
# to create the execution plan).
|
576
|
+
# For post-planning phases, the action is in root when either:
|
577
|
+
# - the @caller_action_id is not set OR
|
578
|
+
# - the @caller_action_id is set but the @caller_execution_plan_id is set as well
|
579
|
+
# which means, the @caller_action_id is actually referencing different execution plan
|
580
|
+
# and this action is creating a new execution plan, that's tracked as sub-plan
|
581
|
+
# for the @caller_execution_plan_id
|
582
|
+
@triggering_action.nil? && (@caller_action_id.nil? || @caller_execution_plan_id)
|
575
583
|
end
|
576
584
|
|
577
585
|
# An action must be a singleton and have a singleton lock
|
data/lib/dynflow/delayed_plan.rb
CHANGED
@@ -34,15 +34,13 @@ module Dynflow
|
|
34
34
|
execution_plan.root_plan_step.state = :error
|
35
35
|
execution_plan.root_plan_step.error = ::Dynflow::ExecutionPlan::Steps::Error.new(message)
|
36
36
|
execution_plan.root_plan_step.save
|
37
|
-
execution_plan.
|
38
|
-
execution_plan.update_state :stopped
|
37
|
+
execution_plan.update_state :stopped, history_notice: history_entry
|
39
38
|
end
|
40
39
|
|
41
40
|
def cancel
|
42
41
|
execution_plan.root_plan_step.state = :cancelled
|
43
42
|
execution_plan.root_plan_step.save
|
44
|
-
execution_plan.
|
45
|
-
execution_plan.update_state :stopped
|
43
|
+
execution_plan.update_state :stopped, history_notice: "Delayed task cancelled"
|
46
44
|
@world.persistence.delete_delayed_plans(:execution_plan_uuid => @execution_plan_uuid)
|
47
45
|
return true
|
48
46
|
end
|
data/lib/dynflow/director.rb
CHANGED
@@ -187,7 +187,7 @@ module Dynflow
|
|
187
187
|
if new_state == :running
|
188
188
|
return manager.restart
|
189
189
|
else
|
190
|
-
manager.execution_plan.
|
190
|
+
manager.execution_plan.update_state(new_state)
|
191
191
|
return false
|
192
192
|
end
|
193
193
|
end
|
@@ -217,20 +217,12 @@ module Dynflow
|
|
217
217
|
case execution_plan.state
|
218
218
|
when :running
|
219
219
|
if execution_plan.error?
|
220
|
-
execution_plan.execution_history.add('pause execution', @world.id)
|
221
220
|
execution_plan.update_state(:paused)
|
222
221
|
elsif manager.done?
|
223
|
-
execution_plan.execution_history.add('finish execution', @world.id)
|
224
222
|
execution_plan.update_state(:stopped)
|
225
223
|
end
|
226
224
|
# If the state is marked as running without errors but manager is not done,
|
227
225
|
# 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
226
|
end
|
235
227
|
end
|
236
228
|
|
@@ -15,7 +15,6 @@ module Dynflow
|
|
15
15
|
unless [:planned, :paused].include? execution_plan.state
|
16
16
|
raise "execution_plan is not in pending or paused state, it's #{execution_plan.state}"
|
17
17
|
end
|
18
|
-
execution_plan.execution_history.add('start execution', @world.id)
|
19
18
|
execution_plan.update_state(:running)
|
20
19
|
end
|
21
20
|
|
@@ -110,7 +110,13 @@ module Dynflow
|
|
110
110
|
@world.logger
|
111
111
|
end
|
112
112
|
|
113
|
-
|
113
|
+
# @param state [Symbol] representing the new state
|
114
|
+
# @param history_notice [Symbol|string|false] should a note to execution_history be added as well?
|
115
|
+
# Possible values:
|
116
|
+
# - :auto (default) - the history notice will be added based on the new state
|
117
|
+
# - string - custom history notice is added
|
118
|
+
# - false - don't add any notice
|
119
|
+
def update_state(state, history_notice: :auto)
|
114
120
|
hooks_to_run = [state]
|
115
121
|
original = self.state
|
116
122
|
case self.state = state
|
@@ -134,6 +140,7 @@ module Dynflow
|
|
134
140
|
end
|
135
141
|
logger.debug format('%13s %s %9s >> %9s',
|
136
142
|
'ExecutionPlan', id, original, state)
|
143
|
+
add_history_notice(history_notice)
|
137
144
|
self.save
|
138
145
|
toggle_telemetry_state original == :pending ? nil : original.to_s,
|
139
146
|
self.state == :stopped ? nil : self.state.to_s
|
@@ -246,7 +253,6 @@ module Dynflow
|
|
246
253
|
def delay(caller_action, action_class, delay_options, *args)
|
247
254
|
save
|
248
255
|
@root_plan_step = add_scheduling_step(action_class, caller_action)
|
249
|
-
execution_history.add("delay", @world.id)
|
250
256
|
serializer = root_plan_step.delay(delay_options, args)
|
251
257
|
delayed_plan = DelayedPlan.new(@world,
|
252
258
|
id,
|
@@ -563,6 +569,22 @@ module Dynflow
|
|
563
569
|
{ :world => @world.id, :action => @label }
|
564
570
|
end
|
565
571
|
|
572
|
+
def add_history_notice(history_notice)
|
573
|
+
if history_notice == :auto
|
574
|
+
history_notice = case state
|
575
|
+
when :running
|
576
|
+
'start execution'
|
577
|
+
when :paused
|
578
|
+
'pause execution'
|
579
|
+
when :stopped
|
580
|
+
'finish execution'
|
581
|
+
when :scheduled
|
582
|
+
'delay'
|
583
|
+
end
|
584
|
+
end
|
585
|
+
execution_history.add(history_notice, @world.id) if history_notice
|
586
|
+
end
|
587
|
+
|
566
588
|
private_class_method :steps_from_hash
|
567
589
|
end
|
568
590
|
# rubocop:enable Metrics/ClassLength
|
@@ -41,7 +41,7 @@ module Dynflow
|
|
41
41
|
end
|
42
42
|
|
43
43
|
{ fatal: 4, error: 3, warn: 2, info: 1, debug: 0 }.each do |method, level|
|
44
|
-
define_method method do |message, &block|
|
44
|
+
define_method method do |message = nil, &block|
|
45
45
|
@logger.add level, message, @prog_name, &block
|
46
46
|
end
|
47
47
|
end
|
data/lib/dynflow/version.rb
CHANGED
@@ -33,8 +33,6 @@ module Dynflow
|
|
33
33
|
# @return [void]
|
34
34
|
def invalidate_execution_lock(execution_lock)
|
35
35
|
with_valid_execution_plan_for_lock(execution_lock) do |plan|
|
36
|
-
plan.execution_history.add('terminate execution', execution_lock.world_id)
|
37
|
-
|
38
36
|
plan.steps.values.each do |step|
|
39
37
|
if step.state == :running
|
40
38
|
step.error = ExecutionPlan::Steps::Error.new("Abnormal termination (previous state: #{step.state})")
|
@@ -43,7 +41,8 @@ module Dynflow
|
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
46
|
-
plan.
|
44
|
+
plan.execution_history.add('terminate execution', execution_lock.world_id)
|
45
|
+
plan.update_state(:paused, history_notice: false) if plan.state == :running
|
47
46
|
plan.save
|
48
47
|
coordinator.release(execution_lock)
|
49
48
|
|
data/test/action_test.rb
CHANGED
@@ -344,10 +344,19 @@ module Dynflow
|
|
344
344
|
end
|
345
345
|
end
|
346
346
|
|
347
|
+
class DummyAction < Dynflow::Action
|
348
|
+
def run; end
|
349
|
+
end
|
350
|
+
|
347
351
|
class ParentAction < Dynflow::Action
|
348
352
|
|
349
353
|
include Dynflow::Action::WithSubPlans
|
350
354
|
|
355
|
+
def plan(*_)
|
356
|
+
super
|
357
|
+
plan_action(DummyAction, {})
|
358
|
+
end
|
359
|
+
|
351
360
|
def create_sub_plans
|
352
361
|
input[:count].times.map { trigger(ChildAction, suspend: input[:suspend]) }
|
353
362
|
end
|
@@ -365,6 +374,7 @@ module Dynflow
|
|
365
374
|
if FailureSimulator.fail_in_child_plan
|
366
375
|
raise "Fail in child plan"
|
367
376
|
end
|
377
|
+
plan_action(DummyAction, {})
|
368
378
|
super
|
369
379
|
end
|
370
380
|
|
@@ -437,6 +447,14 @@ module Dynflow
|
|
437
447
|
sub_plans.each { |sub_plan| sub_plan.caller_execution_plan_id.must_equal execution_plan.id }
|
438
448
|
end
|
439
449
|
|
450
|
+
specify "the parent and sub-plan actions return root_action? properly" do
|
451
|
+
assert execution_plan.actions.first.send(:root_action?), 'main action of parent task should be considered a root_action?'
|
452
|
+
refute execution_plan.actions.last.send(:root_action?), 'sub action of parent task should not be considered a root_action?'
|
453
|
+
sub_plan = execution_plan.sub_plans.first
|
454
|
+
assert sub_plan.actions.first.send(:root_action?), 'main action of sub-task should be considered a root_action?'
|
455
|
+
refute sub_plan.actions.last.send(:root_action?), 'sub action of sub-task should not be considered a root_action?'
|
456
|
+
end
|
457
|
+
|
440
458
|
specify "it saves the information about number for sub plans in the output" do
|
441
459
|
execution_plan.entry_action.output.must_equal('total_count' => 2,
|
442
460
|
'failed_count' => 0,
|
@@ -9,16 +9,19 @@ module Dynflow
|
|
9
9
|
|
10
10
|
class Flag
|
11
11
|
class << self
|
12
|
+
attr_accessor :raised_count
|
13
|
+
|
12
14
|
def raise!
|
13
|
-
|
15
|
+
self.raised_count ||= 0
|
16
|
+
self.raised_count += 1
|
14
17
|
end
|
15
18
|
|
16
19
|
def raised?
|
17
|
-
|
20
|
+
raised_count > 0
|
18
21
|
end
|
19
22
|
|
20
23
|
def lower!
|
21
|
-
|
24
|
+
self.raised_count = 0
|
22
25
|
end
|
23
26
|
end
|
24
27
|
end
|
@@ -32,6 +35,10 @@ module Dynflow
|
|
32
35
|
Flag.raise!
|
33
36
|
raise "A controlled failure"
|
34
37
|
end
|
38
|
+
|
39
|
+
def raise_flag_root_only(_execution_plan)
|
40
|
+
Flag.raise! if root_action?
|
41
|
+
end
|
35
42
|
end
|
36
43
|
|
37
44
|
class ActionWithHooks < ::Dynflow::Action
|
@@ -46,12 +53,39 @@ module Dynflow
|
|
46
53
|
execution_plan_hooks.use :controlled_failure, :on => :stopped
|
47
54
|
end
|
48
55
|
|
56
|
+
class RootOnlyAction < ::Dynflow::Action
|
57
|
+
include FlagHook
|
58
|
+
|
59
|
+
execution_plan_hooks.use :raise_flag_root_only, :on => :stopped
|
60
|
+
end
|
61
|
+
|
62
|
+
class ComposedAction < RootOnlyAction
|
63
|
+
def plan
|
64
|
+
plan_action(RootOnlyAction)
|
65
|
+
plan_action(RootOnlyAction)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
49
69
|
class ActionOnFailure < ::Dynflow::Action
|
50
70
|
include FlagHook
|
51
71
|
|
52
72
|
execution_plan_hooks.use :raise_flag, :on => :failure
|
53
73
|
end
|
54
74
|
|
75
|
+
class ActionOnPause < ::Dynflow::Action
|
76
|
+
include FlagHook
|
77
|
+
|
78
|
+
def run
|
79
|
+
error!("pause")
|
80
|
+
end
|
81
|
+
|
82
|
+
def rescue_strategy
|
83
|
+
Dynflow::Action::Rescue::Pause
|
84
|
+
end
|
85
|
+
|
86
|
+
execution_plan_hooks.use :raise_flag, :on => :paused
|
87
|
+
end
|
88
|
+
|
55
89
|
class Inherited < ActionWithHooks; end
|
56
90
|
class Overriden < ActionWithHooks
|
57
91
|
execution_plan_hooks.do_not_use :raise_flag
|
@@ -67,6 +101,28 @@ module Dynflow
|
|
67
101
|
assert Flag.raised?
|
68
102
|
end
|
69
103
|
|
104
|
+
it 'runs the on_pause hook' do
|
105
|
+
refute Flag.raised?
|
106
|
+
plan = world.trigger(ActionOnPause)
|
107
|
+
plan.finished.wait!
|
108
|
+
assert Flag.raised?
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'with auto_rescue' do
|
112
|
+
let(:world) do
|
113
|
+
WorldFactory.create_world do |config|
|
114
|
+
config.auto_rescue = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'runs the on_pause hook' do
|
119
|
+
refute Flag.raised?
|
120
|
+
plan = world.trigger(ActionOnPause)
|
121
|
+
plan.finished.wait!
|
122
|
+
assert Flag.raised?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
70
126
|
it 'runs the on_failure hook on cancel' do
|
71
127
|
refute Flag.raised?
|
72
128
|
@start_at = Time.now.utc + 180
|
@@ -97,6 +153,13 @@ module Dynflow
|
|
97
153
|
plan.finished.wait!
|
98
154
|
refute Flag.raised?
|
99
155
|
end
|
156
|
+
|
157
|
+
it 'can determine in side the hook, whether the hook is running for root action or sub-action' do
|
158
|
+
refute Flag.raised?
|
159
|
+
plan = world.trigger(ComposedAction)
|
160
|
+
plan.finished.wait!
|
161
|
+
Flag.raised_count.must_equal 1
|
162
|
+
end
|
100
163
|
end
|
101
164
|
end
|
102
165
|
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.3
|
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-04-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|