dynflow 1.2.2 → 1.2.3
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/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
|