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 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