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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddba15f840b26c5148fcebf8a0b69ff22abc0bba1fe70d2fa02e32722003011b
4
- data.tar.gz: 4c7c6a7c3d23e1e0684ef61fa44ccf1cd44ff76cfba9c0e8e3b0587b9674ffeb
3
+ metadata.gz: 403ea79d6ea134104cc54ccaca3edbdce414306b944ccd957f37421ef7070d65
4
+ data.tar.gz: 7361cc2b7e099d7b537738880dd4197034a7323b9e386c6e937518518931cadc
5
5
  SHA512:
6
- metadata.gz: a5b836d767ef81131f217d82467d96410c63140bb38e247489b33b9386df2ddb30aff56da3b42bff216fd6a419f714176d0e111415e6793c3c534bdbfcbd6938
7
- data.tar.gz: 01a74517eb6500b5e986759d017f2886c1a5032d81e026d1e41450033935dfab9c342bf4ced411da56b2cf3daefd79a82a1137d97030567bbcbe517a4e9599cb
6
+ metadata.gz: 0b8f9367989b27fc3a55294659441c3d0360ccc87d752cebd1f723d5a68400083ec0a0330a29811b868b87b459bf651a1c8fc3bce5c8399556cc26671c0c3d54
7
+ data.tar.gz: 95248248daebcf5d83b9be6bf3593a1ed55b41d5ef048180d30464091810d36361bdf3bdbc688f58625cdd6f5fdbb2a558fd1d8f56063b21cf65c8f03a8f8458
data/Gemfile CHANGED
@@ -28,6 +28,10 @@ if RUBY_VERSION < "2.2.2"
28
28
  gem 'sinatra', '~> 1.4.8'
29
29
  end
30
30
 
31
+ if RUBY_VERSION < '2.3.0'
32
+ gem 'i18n', '<= 1.5.1'
33
+ end
34
+
31
35
  group :lint do
32
36
  gem 'rubocop', '0.39.0'
33
37
  end
@@ -571,7 +571,15 @@ module Dynflow
571
571
  end
572
572
 
573
573
  def root_action?
574
- @triggering_action.nil?
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
@@ -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.execution_history.add history_entry, @world.id unless history_entry.nil?
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.execution_history.add "Delayed task cancelled", @world.id
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
@@ -187,7 +187,7 @@ module Dynflow
187
187
  if new_state == :running
188
188
  return manager.restart
189
189
  else
190
- manager.execution_plan.state = new_state
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
- def update_state(state)
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
@@ -7,7 +7,7 @@ module Dynflow
7
7
  end
8
8
 
9
9
  [:fatal, :error, :warn, :info, :debug].each do |method|
10
- define_method method do |message, &block|
10
+ define_method method do |message = nil, &block|
11
11
  if block
12
12
  @base.send method, &-> { format(block.call) }
13
13
  else
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '1.2.2'.freeze
2
+ VERSION = '1.2.3'.freeze
3
3
  end
@@ -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.update_state(:paused) if plan.state == :running
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
 
@@ -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
- @raised = true
15
+ self.raised_count ||= 0
16
+ self.raised_count += 1
14
17
  end
15
18
 
16
19
  def raised?
17
- @raised
20
+ raised_count > 0
18
21
  end
19
22
 
20
23
  def lower!
21
- @raised = false
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.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-02-27 00:00:00.000000000 Z
12
+ date: 2019-04-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json