dynflow 0.5.1 → 0.6.0

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