dynflow 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -81,6 +81,8 @@ module Dynflow
81
81
  attr_reader :world, :phase, :execution_plan_id, :id, :input,
82
82
  :plan_step_id, :run_step_id, :finalize_step_id
83
83
 
84
+ middleware.use Action::Progress::Calculate
85
+
84
86
  def initialize(attributes, world)
85
87
  Type! attributes, Hash
86
88
 
@@ -6,44 +6,104 @@ module Dynflow
6
6
  def run(event = nil)
7
7
  case event
8
8
  when nil
9
- self.external_task = invoke_external_task
10
- suspend_and_ping
9
+ if external_task
10
+ resume_external_action
11
+ else
12
+ initiate_external_action
13
+ end
11
14
  when Poll
12
- self.external_task = poll_external_task
13
- suspend_and_ping unless done?
15
+ poll_external_task_with_rescue
14
16
  else
15
17
  raise "unrecognized event #{event}"
16
18
  end
17
19
  end
18
20
 
19
- def external_task
21
+ def done?
20
22
  raise NotImplementedError
21
23
  end
22
24
 
23
- def done?
25
+ def invoke_external_task
24
26
  raise NotImplementedError
25
27
  end
26
28
 
27
- private
28
-
29
- def invoke_external_task
29
+ def poll_external_task
30
30
  raise NotImplementedError
31
31
  end
32
32
 
33
+ # External task data. It should return nil when the task has not
34
+ # been triggered yet.
35
+ def external_task
36
+ output[:task]
37
+ end
38
+
33
39
  def external_task=(external_task_data)
34
- raise NotImplementedError
40
+ output[:task] = external_task_data
35
41
  end
36
42
 
37
- def poll_external_task
38
- raise NotImplementedError
43
+ # What is the trend in waiting for next polling event. It allows
44
+ # to strart with frequent polling, but slow down once it's clear this
45
+ # task will take some time: the idea is we don't care that much in finishing
46
+ # few seconds sooner, when the task takes orders of minutes/hours. It allows
47
+ # not overwhelming the backend-servers with useless requests.
48
+ # By default, it switches to next interval after +attempts_before_next_interval+ number
49
+ # of attempts
50
+ def poll_intervals
51
+ [0.5, 1, 2, 4, 8, 16]
52
+ end
53
+
54
+ def attempts_before_next_interval
55
+ 5
56
+ end
57
+
58
+ # Returns the time to wait between two polling intervals.
59
+ def poll_interval
60
+ interval_level = poll_attempts[:total]/attempts_before_next_interval
61
+ poll_intervals[interval_level] || poll_intervals.last
62
+ end
63
+
64
+ # How man times in row should we retry the polling before giving up
65
+ def poll_max_retries
66
+ 3
67
+ end
68
+
69
+ def initiate_external_action
70
+ self.external_task = invoke_external_task
71
+ suspend_and_ping unless done?
72
+ end
73
+
74
+ def resume_external_action
75
+ poll_external_task_with_rescue
76
+ rescue
77
+ initiate_external_action
39
78
  end
40
79
 
41
80
  def suspend_and_ping
42
81
  suspend { |suspended_action| world.clock.ping suspended_action, poll_interval, Poll }
43
82
  end
44
83
 
45
- def poll_interval
46
- 0.5
84
+ def poll_external_task_with_rescue
85
+ poll_attempts[:total] += 1
86
+ self.external_task = poll_external_task
87
+ poll_attempts[:failed] = 0
88
+ suspend_and_ping unless done?
89
+ rescue => error
90
+ poll_attempts[:failed] += 1
91
+ rescue_poll_external_task(error)
47
92
  end
93
+
94
+ def poll_attempts
95
+ output[:poll_attempts] ||= { total: 0, failed: 0 }
96
+ end
97
+
98
+ def rescue_poll_external_task(error)
99
+ if poll_attempts[:failed] < poll_max_retries
100
+ action_logger.warn("Polling failed, attempt no. #{poll_attempts[:failed]}, retrying in #{poll_interval}")
101
+ action_logger.warn(error)
102
+ suspend_and_ping
103
+ else
104
+ raise error
105
+ end
106
+ end
107
+
48
108
  end
49
109
  end
@@ -10,6 +10,37 @@ module Dynflow
10
10
  # the progress is 1 for success/skipped actions and 0 for errorneous ones.
11
11
  module Action::Progress
12
12
 
13
+ class Calculate < Middleware
14
+
15
+ def run(*args)
16
+ with_progress_calculation(*args) do
17
+ [action.run_progress, action.run_progress_weight]
18
+ end
19
+ end
20
+
21
+ def finalize(*args)
22
+ with_progress_calculation(*args) do
23
+ [action.finalize_progress, action.finalize_progress_weight]
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def with_progress_calculation(*args)
30
+ pass(*args)
31
+ ensure
32
+ begin
33
+ action.calculated_progress = yield
34
+ rescue => error
35
+ # we don't want progress calculation to cause fail of the whole process
36
+ # TODO: introduce post-execute state for handling issues with additional
37
+ # calculations after the step is run
38
+ action.action_logger.error('Error in progress calculation')
39
+ action.action_logger.error(error)
40
+ end
41
+ end
42
+ end
43
+
13
44
  def run_progress
14
45
  0.5
15
46
  end
@@ -26,37 +57,7 @@ module Dynflow
26
57
  1
27
58
  end
28
59
 
29
- # this method is not intended to be overriden. Use +{run, finalize}_progress+
30
- # variants instead
31
- def progress_done
32
- case self.state
33
- when :success, :skipped
34
- 1
35
- when :running, :suspended
36
- case phase
37
- when Action::Run
38
- run_progress
39
- when Action::Finalize
40
- finalize_progress
41
- else
42
- raise 'Calculating progress for this phase is not supported'
43
- end
44
- else
45
- 0
46
- end
47
- end
48
-
49
- def progress_weight
50
- case phase
51
- when Action::Run
52
- run_progress_weight
53
- when Action::Finalize
54
- finalize_progress_weight
55
- else
56
- raise 'Calculating progress for this phase is not supported'
57
- end
58
- end
59
-
60
+ attr_accessor :calculated_progress
60
61
  end
61
62
  end
62
63
 
@@ -37,7 +37,7 @@ module Dynflow
37
37
  steps = {},
38
38
  started_at = nil,
39
39
  ended_at = nil,
40
- execution_time = 0.0,
40
+ execution_time = nil,
41
41
  real_time = 0.0)
42
42
 
43
43
  @id = Type! id, String
@@ -48,8 +48,8 @@ module Dynflow
48
48
  @root_plan_step = root_plan_step
49
49
  @started_at = Type! started_at, Time, NilClass
50
50
  @ended_at = Type! ended_at, Time, NilClass
51
- @execution_time = Type! execution_time, Float
52
- @real_time = Type! real_time, Float
51
+ @execution_time = Type! execution_time, Numeric, NilClass
52
+ @real_time = Type! real_time, Numeric
53
53
 
54
54
  steps.all? do |k, v|
55
55
  Type! k, Integer
@@ -68,8 +68,9 @@ module Dynflow
68
68
  when :planning
69
69
  @started_at = Time.now
70
70
  when :stopped
71
- @ended_at = Time.now
72
- @real_time = @ended_at - @started_at
71
+ @ended_at = Time.now
72
+ @real_time = @ended_at - @started_at
73
+ @execution_time = compute_execution_time
73
74
  else
74
75
  # ignore
75
76
  end
@@ -78,10 +79,6 @@ module Dynflow
78
79
  self.save
79
80
  end
80
81
 
81
- def update_execution_time(execution_time)
82
- @execution_time += execution_time
83
- end
84
-
85
82
  def result
86
83
  all_steps = steps.values
87
84
  if all_steps.any? { |step| step.state == :error }
@@ -205,6 +202,7 @@ module Dynflow
205
202
 
206
203
  def add_run_step(action)
207
204
  add_step(Steps::RunStep, action.class, action.id).tap do |step|
205
+ step.progress_weight = action.run_progress_weight
208
206
  @dependency_graph.add_dependencies(step, action)
209
207
  current_run_flow.add_and_resolve(@dependency_graph, Flows::Atom.new(step.id))
210
208
  end
@@ -212,6 +210,7 @@ module Dynflow
212
210
 
213
211
  def add_finalize_step(action)
214
212
  add_step(Steps::FinalizeStep, action.class, action.id).tap do |step|
213
+ step.progress_weight = action.finalize_progress_weight
215
214
  finalize_flow << Flows::Atom.new(step.id)
216
215
  end
217
216
  end
@@ -252,14 +251,20 @@ module Dynflow
252
251
  hash[:real_time])
253
252
  end
254
253
 
254
+ def compute_execution_time
255
+ self.steps.values.reduce(0) do |execution_time, step|
256
+ execution_time + (step.execution_time || 0)
257
+ end
258
+ end
259
+
255
260
  # @return [0..1] the percentage of the progress. See Action::Progress for more
256
261
  # info
257
262
  def progress
258
263
  flow_step_ids = run_flow.all_step_ids + finalize_flow.all_step_ids
259
264
  plan_done, plan_total = flow_step_ids.reduce([0.0, 0]) do |(done, total), step_id|
260
- step_progress_done, step_progress_weight = self.steps[step_id].progress
261
- [done + (step_progress_done * step_progress_weight),
262
- total + step_progress_weight]
265
+ step = self.steps[step_id]
266
+ [done + (step.progress_done * step.progress_weight),
267
+ total + step.progress_weight]
263
268
  end
264
269
  plan_total > 0 ? (plan_done / plan_total) : 1
265
270
  end
@@ -18,7 +18,9 @@ module Dynflow
18
18
  started_at = nil,
19
19
  ended_at = nil,
20
20
  execution_time = 0.0,
21
- real_time = 0.0)
21
+ real_time = 0.0,
22
+ progress_done = nil,
23
+ progress_weight = nil)
22
24
 
23
25
  @id = id || raise(ArgumentError, 'missing id')
24
26
  @execution_plan_id = Type! execution_plan_id, String
@@ -26,8 +28,11 @@ module Dynflow
26
28
  @error = Type! error, ExecutionPlan::Steps::Error, NilClass
27
29
  @started_at = Type! started_at, Time, NilClass
28
30
  @ended_at = Type! ended_at, Time, NilClass
29
- @execution_time = Type! execution_time, Float
30
- @real_time = Type! real_time, Float
31
+ @execution_time = Type! execution_time, Numeric
32
+ @real_time = Type! real_time, Numeric
33
+
34
+ @progress_done = Type! progress_done, Numeric, NilClass
35
+ @progress_weight = Type! progress_weight, Numeric, NilClass
31
36
 
32
37
  self.state = state.to_sym
33
38
 
@@ -76,16 +81,31 @@ module Dynflow
76
81
  started_at: time_to_str(started_at),
77
82
  ended_at: time_to_str(ended_at),
78
83
  execution_time: execution_time,
79
- real_time: real_time
84
+ real_time: real_time,
85
+ progress_done: progress_done,
86
+ progress_weight: progress_weight
87
+ end
88
+
89
+ def progress_done
90
+ default_progress_done || @progress_done || 0
80
91
  end
81
92
 
82
- # @return [Array<[0..100], Fixnum>] the percentage of the step progress
83
- # and the weight - how time-consuming the task is comparing the others.
84
- # @see [Action::Progress] for more details
85
- def progress
86
- raise NotImplementedError, "Expected to be implemented in RunStep and FinalizeStep"
93
+ # in specific states it's clear what progress the step is in
94
+ def default_progress_done
95
+ case self.state
96
+ when :success, :skipped
97
+ 1
98
+ when :pending
99
+ 0
100
+ end
87
101
  end
88
102
 
103
+ def progress_weight
104
+ @progress_weight || 0 # 0 means not calculated yet
105
+ end
106
+
107
+ attr_writer :progress_weight # to allow setting the weight from planning
108
+
89
109
  # @return [Action] in presentation mode, intended for retrieving: progress information,
90
110
  # details, human outputs, etc.
91
111
  def action(execution_plan)
@@ -108,16 +128,19 @@ module Dynflow
108
128
  string_to_time(hash[:started_at]),
109
129
  string_to_time(hash[:ended_at]),
110
130
  hash[:execution_time],
111
- hash[:real_time]
131
+ hash[:real_time],
132
+ hash[:progress_done],
133
+ hash[:progress_weight]
112
134
  end
113
135
 
114
136
  private
115
137
 
116
- def with_time_calculation(&block)
138
+ def with_meta_calculation(action, &block)
117
139
  start = Time.now
118
140
  @started_at ||= start
119
141
  block.call
120
142
  ensure
143
+ @progress_done, @progress_weight = action.calculated_progress
121
144
  @ended_at = Time.now
122
145
  @execution_time += @ended_at - start
123
146
  @real_time = @ended_at - @started_at
@@ -5,7 +5,7 @@ module Dynflow
5
5
  def execute(*args)
6
6
  return self if [:skipped, :success].include? self.state
7
7
  open_action do |action|
8
- with_time_calculation do
8
+ with_meta_calculation(action) do
9
9
  action.execute(*args)
10
10
  end
11
11
  end
@@ -15,11 +15,6 @@ module Dynflow
15
15
  self.class.from_hash(to_hash, execution_plan_id, world)
16
16
  end
17
17
 
18
- def progress
19
- action = persistence.load_action(self)
20
- [action.progress_done, action.progress_weight]
21
- end
22
-
23
18
  private
24
19
 
25
20
  def open_action
@@ -50,12 +50,10 @@ module Dynflow
50
50
  action = action_class.new(attributes, execution_plan.world)
51
51
  persistence.save_action(execution_plan_id, action)
52
52
 
53
- with_time_calculation do
53
+ with_meta_calculation(action) do
54
54
  action.execute(*args)
55
55
  end
56
56
 
57
- execution_plan.update_execution_time execution_time
58
-
59
57
  persistence.save_action(execution_plan_id, action)
60
58
  return action
61
59
  end
@@ -51,15 +51,8 @@ module Dynflow
51
51
  execution_plan.steps[step.id] = step
52
52
  suspended, work = @running_steps_manager.done(step)
53
53
  unless suspended
54
- execution_plan.update_execution_time step.execution_time
55
54
  work = compute_next_from_step.call step
56
55
  end
57
- # TODO: can be probably disabled to improve
58
- # performance, execution time will not be updated,
59
- # maybe more - check on the other side, it allows
60
- # us to use persistence adapter for hooking into
61
- # the running process.
62
- execution_plan.save
63
56
  work
64
57
  end),
65
58
  (on Work::Finalize do
@@ -70,8 +70,6 @@ module Dynflow
70
70
 
71
71
  def run_step(step)
72
72
  step.execute
73
- execution_plan.update_execution_time step.execution_time
74
- execution_plan.save
75
73
  return step.state != :error
76
74
  end
77
75
 
@@ -25,7 +25,7 @@ module Dynflow
25
25
 
26
26
  META_DATA = { execution_plan: %w(state result started_at ended_at real_time execution_time),
27
27
  action: [],
28
- step: %w(state started_at ended_at real_time execution_time action_id) }
28
+ step: %w(state started_at ended_at real_time execution_time action_id progress_done progress_weight) }
29
29
 
30
30
  def initialize(db_path)
31
31
  @db = initialize_db db_path
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:dynflow_steps) do
4
+ add_column :progress_done, Float
5
+ add_column :progress_weight, Float
6
+ end
7
+ end
8
+ end
@@ -3,7 +3,7 @@ module Dynflow
3
3
  extend Algebrick::TypeCheck
4
4
 
5
5
  def self.logger_adapter
6
- @logger_adapter ||= LoggerAdapters::Simple.new $stdout, 0
6
+ @logger_adapter || LoggerAdapters::Simple.new($stdout, 1)
7
7
  end
8
8
 
9
9
  def self.logger_adapter=(adapter)
@@ -1,22 +1,38 @@
1
1
  module Dynflow
2
2
  module Testing
3
3
  class ManagedClock
4
+
5
+ attr_reader :pending_pings
6
+
7
+ include Algebrick::Types
8
+ Timer = Algebrick.type do
9
+ fields! who: Object, # to ping back
10
+ when: type { variants Time, Numeric }, # to deliver
11
+ what: Maybe[Object], # to send
12
+ where: Symbol # it should be delivered, which method
13
+ end
14
+
15
+ module Timer
16
+ include Clock::Timer
17
+ end
18
+
4
19
  def initialize
5
- @pings_to_process = []
20
+ @pending_pings = []
6
21
  end
7
22
 
8
23
  def ping(who, time, with_what = nil, where = :<<)
9
- @pings_to_process << [who, [where, with_what].compact]
24
+ with = with_what.nil? ? None : Some[Object][with_what]
25
+ @pending_pings << Timer[who, time, with, where]
10
26
  end
11
27
 
12
28
  def progress
13
- copy = @pings_to_process.dup
29
+ copy = @pending_pings.dup
14
30
  clear
15
- copy.each { |who, args| who.send *args }
31
+ copy.each { |ping| ping.apply }
16
32
  end
17
33
 
18
34
  def clear
19
- @pings_to_process.clear
35
+ @pending_pings.clear
20
36
  end
21
37
  end
22
38
  end
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '0.5.1'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -126,11 +126,11 @@ module Dynflow
126
126
  end
127
127
  end
128
128
 
129
- def progress_width(action)
130
- if action.state == :error
129
+ def progress_width(step)
130
+ if step.state == :error
131
131
  100 # we want to show the red bar in full width
132
132
  else
133
- action.progress_done * 100
133
+ step.progress_done * 100
134
134
  end
135
135
  end
136
136
 
data/test/action_test.rb CHANGED
@@ -91,5 +91,166 @@ module Dynflow
91
91
  end
92
92
  end
93
93
 
94
+ describe 'polling action' do
95
+ CWE = Support::CodeWorkflowExample
96
+ include Dynflow::Testing
97
+
98
+ class ExternalService
99
+ def invoke(args)
100
+ reset!
101
+ end
102
+
103
+ def poll(id)
104
+ raise 'fail' if @current_state[:failing]
105
+ @current_state[:progress] += 10
106
+ return @current_state
107
+ end
108
+
109
+ def reset!
110
+ @current_state = { task_id: 123, progress: 0 }
111
+ end
112
+
113
+ def will_fail
114
+ @current_state[:failing] = true
115
+ end
116
+
117
+ def wont_fail
118
+ @current_state.delete(:failing)
119
+ end
120
+ end
121
+
122
+ class TestPollingAction < Dynflow::Action
123
+
124
+ class Config
125
+ attr_accessor :external_service, :poll_max_retries,
126
+ :poll_intervals, :attempts_before_next_interval
127
+
128
+ def initialize
129
+ @external_service = ExternalService.new
130
+ @poll_max_retries = 2
131
+ @poll_intervals = [0.5, 1]
132
+ @attempts_before_next_interval = 2
133
+ end
134
+ end
135
+
136
+ include Dynflow::Action::Polling
137
+
138
+ def invoke_external_task
139
+ external_service.invoke(input[:task_args])
140
+ end
141
+
142
+ def poll_external_task
143
+ external_service.poll(external_task[:task_id])
144
+ end
145
+
146
+ def done?
147
+ external_task && external_task[:progress] >= 100
148
+ end
149
+
150
+ def poll_max_retries
151
+ self.class.config.poll_max_retries
152
+ end
153
+
154
+ def poll_intervals
155
+ self.class.config.poll_intervals
156
+ end
157
+
158
+ def attempts_before_next_interval
159
+ self.class.config.attempts_before_next_interval
160
+ end
161
+
162
+ class << self
163
+ def config
164
+ @config ||= Config.new
165
+ end
166
+
167
+ attr_writer :config
168
+ end
169
+
170
+ def external_service
171
+ self.class.config.external_service
172
+ end
173
+ end
174
+
175
+ let(:plan) do
176
+ create_and_plan_action TestPollingAction, { task_args: 'do something' }
177
+ end
178
+
179
+ before do
180
+ TestPollingAction.config = TestPollingAction::Config.new
181
+ end
182
+
183
+ def next_ping(action)
184
+ action.world.clock.pending_pings.first
185
+ end
186
+
187
+ it 'initiates the external task' do
188
+ action = run_action plan
189
+
190
+ action.output[:task][:task_id].must_equal 123
191
+ end
192
+
193
+ it 'polls till the task is done' do
194
+ action = run_action plan
195
+
196
+ 9.times { progress_action_time action }
197
+ action.done?.must_equal false
198
+ next_ping(action).wont_be_nil
199
+ action.state.must_equal :suspended
200
+
201
+ progress_action_time action
202
+ action.done?.must_equal true
203
+ next_ping(action).must_be_nil
204
+ action.state.must_equal :success
205
+ end
206
+
207
+ it 'tries to poll for the old task when resuming' do
208
+ action = run_action plan
209
+ action.output[:task][:progress].must_equal 0
210
+ run_action action
211
+ action.output[:task][:progress].must_equal 10
212
+ end
213
+
214
+ it 'invokes the external task again when polling on the old one fails' do
215
+ action = run_action plan
216
+ action.world.action_logger.level = 3
217
+ action.external_service.will_fail
218
+ action.output[:task][:progress].must_equal 0
219
+ run_action action
220
+ action.output[:task][:progress].must_equal 0
221
+ end
222
+
223
+ it 'tolerates some failure while polling' do
224
+ action = run_action plan
225
+ action.external_service.will_fail
226
+ action.world.action_logger.level = 4
227
+
228
+ TestPollingAction.config.poll_max_retries = 3
229
+ (1..2).each do |attempt|
230
+ progress_action_time action
231
+ action.poll_attempts[:failed].must_equal attempt
232
+ next_ping(action).wont_be_nil
233
+ action.state.must_equal :suspended
234
+ end
235
+
236
+ progress_action_time action
237
+ action.poll_attempts[:failed].must_equal 3
238
+ next_ping(action).must_be_nil
239
+ action.state.must_equal :error
240
+ end
241
+
242
+ it 'allows increasing poll interval in a time' do
243
+ TestPollingAction.config.poll_intervals = [1, 2]
244
+ TestPollingAction.config.attempts_before_next_interval = 1
245
+
246
+ action = run_action plan
247
+ next_ping(action).when.must_equal 1
248
+ progress_action_time action
249
+ next_ping(action).when.must_equal 2
250
+ progress_action_time action
251
+ next_ping(action).when.must_equal 2
252
+ end
253
+
254
+ end
94
255
  end
95
256
  end
@@ -203,8 +203,8 @@ module Dynflow
203
203
  result.result.must_equal :success
204
204
  result.state.must_equal :stopped
205
205
  action = world.persistence.load_action result.steps[2]
206
- action.output[:progress].must_equal 30
207
- action.output[:cancelled].must_equal true
206
+ action.output[:task][:progress].must_equal 30
207
+ action.output[:task][:cancelled].must_equal true
208
208
  end
209
209
  end
210
210
 
@@ -219,7 +219,7 @@ module Dynflow
219
219
  step = result.steps[2]
220
220
  step.error.message.must_equal 'action cancelled'
221
221
  action = world.persistence.load_action step
222
- action.output[:progress].must_equal 30
222
+ action.output[:task][:progress].must_equal 30
223
223
  end
224
224
  end
225
225
 
@@ -241,8 +241,8 @@ module Dynflow
241
241
  result.result.must_equal :success
242
242
  result.state.must_equal :stopped
243
243
  action = world.persistence.load_action result.steps[2]
244
- action.output[:progress].must_be :<=, 30
245
- action.output[:cancelled].must_equal true
244
+ action.output[:task][:progress].must_be :<=, 30
245
+ action.output[:task][:cancelled].must_equal true
246
246
  end
247
247
  end
248
248
  end
@@ -290,22 +290,14 @@ module Support
290
290
 
291
291
  def cancel_external_task
292
292
  if input[:text] !~ /cancel-fail/
293
- { cancelled: true }
293
+ external_task.merge(cancelled: true)
294
294
  else
295
295
  error! 'action cancelled'
296
296
  end
297
297
  end
298
298
 
299
- def external_task=(external_task_data)
300
- self.output.update external_task_data
301
- end
302
-
303
- def external_task
304
- output
305
- end
306
-
307
299
  def done?
308
- external_task[:progress] >= 100
300
+ external_task && external_task[:progress] >= 100
309
301
  end
310
302
 
311
303
  def poll_interval
@@ -313,7 +305,7 @@ module Support
313
305
  end
314
306
 
315
307
  def run_progress
316
- output[:progress].to_f / 100
308
+ external_task && external_task[:progress].to_f / 100
317
309
  end
318
310
  end
319
311
 
@@ -325,14 +317,6 @@ module Support
325
317
  { progress: 0, done: false }
326
318
  end
327
319
 
328
- def external_task=(external_task_data)
329
- self.output.update external_task_data
330
- end
331
-
332
- def external_task
333
- output
334
- end
335
-
336
320
  def poll_external_task
337
321
  if input[:text] == 'troll progress' && !output[:trolled]
338
322
  output[:trolled] = true
@@ -340,15 +324,15 @@ module Support
340
324
  end
341
325
 
342
326
  if input[:text] =~ /pause in progress (\d+)/
343
- TestPause.pause if output[:progress] == $1.to_i
327
+ TestPause.pause if external_task[:progress] == $1.to_i
344
328
  end
345
329
 
346
- progress = output[:progress] + 10
330
+ progress = external_task[:progress] + 10
347
331
  { progress: progress, done: progress >= 100 }
348
332
  end
349
333
 
350
334
  def done?
351
- external_task[:progress] >= 100
335
+ external_task && external_task[:progress] >= 100
352
336
  end
353
337
 
354
338
  def poll_interval
@@ -356,7 +340,7 @@ module Support
356
340
  end
357
341
 
358
342
  def run_progress
359
- output[:progress].to_f / 100
343
+ external_task && external_task[:progress].to_f / 100
360
344
  end
361
345
  end
362
346
 
data/test/test_helper.rb CHANGED
@@ -17,8 +17,6 @@ require 'pry'
17
17
  require 'support/code_workflow_example'
18
18
  require 'support/middleware_example'
19
19
 
20
- Dynflow::Testing.logger_adapter.level = 1
21
-
22
20
  class TestExecutionLog
23
21
 
24
22
  include Enumerable
data/test/testing_test.rb CHANGED
@@ -45,7 +45,6 @@ module Dynflow
45
45
  action.world.must_equal plan.world
46
46
  action.run_step_id.wont_equal action.plan_step_id
47
47
  action.state.must_equal :success
48
- action.progress_done.must_equal 1
49
48
  end
50
49
 
51
50
  specify '#run_action with suspend' do
@@ -53,21 +52,26 @@ module Dynflow
53
52
  plan = create_and_plan_action CWE::DummySuspended, input
54
53
  action = run_action plan
55
54
 
56
- action.output.must_equal 'progress' => 0, 'done' => false
57
- action.progress_done.must_equal 0
55
+ action.output.must_equal 'task' => { 'progress' => 0, 'done' => false }
56
+ action.run_progress.must_equal 0
58
57
 
59
58
  3.times { progress_action_time action }
60
- action.output.must_equal 'progress' => 30, 'done' => false
61
- action.progress_done.must_equal 0.3
59
+ action.output.must_equal('task' => { 'progress' => 30, 'done' => false } ,
60
+ 'poll_attempts' => {'total' => 2, 'failed'=> 0 })
61
+ action.run_progress.must_equal 0.3
62
62
 
63
63
  run_action action, Dynflow::Action::Polling::Poll
64
64
  run_action action, Dynflow::Action::Polling::Poll
65
- action.output.must_equal 'progress' => 50, 'done' => false
66
- action.progress_done.must_equal 0.5
65
+ action.output.must_equal('task' => { 'progress' => 50, 'done' => false },
66
+ 'poll_attempts' => {'total' => 4, 'failed' => 0 })
67
+ action.run_progress.must_equal 0.5
67
68
 
68
69
  5.times { progress_action_time action }
69
- action.output.must_equal 'progress' => 100, 'done' => true
70
- action.progress_done.must_equal 1
70
+
71
+
72
+ action.output.must_equal('task' => { 'progress' => 100, 'done' => true },
73
+ 'poll_attempts' => {'total' => 9, 'failed' => 0 })
74
+ action.run_progress.must_equal 1
71
75
  end
72
76
 
73
77
  specify '#finalize_action' do
@@ -84,7 +88,6 @@ module Dynflow
84
88
  action.world.must_equal plan.world
85
89
  action.finalize_step_id.wont_equal action.run_step_id
86
90
  action.state.must_equal :success
87
- action.progress_done.must_equal 1
88
91
 
89
92
  $dummy_heavy_progress.must_equal 'dummy_heavy_progress'
90
93
  end
@@ -1,7 +1,7 @@
1
1
  <% action = load_action(step) %>
2
2
 
3
3
  <% if flow.is_a? Dynflow::Flows::Atom %>
4
- <div class="<%= h(atom_css_classes(flow)) %>" style=" width: <%= progress_width(action) %>%;"></div>
4
+ <div class="<%= h(atom_css_classes(flow)) %>" style=" width: <%= progress_width(step) %>%;"></div>
5
5
  <% end %>
6
6
 
7
7
  <span class="step-label">
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: 0.5.1
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-13 00:00:00.000000000 Z
12
+ date: 2014-03-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -286,6 +286,7 @@ files:
286
286
  - lib/dynflow/persistence_adapters/abstract.rb
287
287
  - lib/dynflow/persistence_adapters/sequel.rb
288
288
  - lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb
289
+ - lib/dynflow/persistence_adapters/sequel_migrations/002_incremental_progress.rb
289
290
  - lib/dynflow/serializable.rb
290
291
  - lib/dynflow/simple_world.rb
291
292
  - lib/dynflow/stateful.rb