dynflow 0.7.6 → 0.7.7
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.
- data/README.md +11 -3
- data/doc/pages/.gitignore +7 -0
- data/doc/pages/Gemfile +8 -0
- data/doc/pages/Rakefile +26 -0
- data/doc/pages/_config.yml +38 -0
- data/doc/pages/plugins/alert_block.rb +27 -0
- data/doc/pages/plugins/div_tag.rb +24 -0
- data/doc/pages/plugins/graphviz.rb +121 -0
- data/doc/pages/plugins/plantuml.rb +85 -0
- data/doc/pages/plugins/play.rb +13 -0
- data/doc/pages/plugins/tags.rb +138 -0
- data/doc/pages/plugins/toc.rb +20 -0
- data/doc/pages/source/.nojekyll +0 -0
- data/doc/pages/source/404.md +6 -0
- data/doc/pages/source/_includes/disqus.html +25 -0
- data/doc/pages/source/_includes/google_analytics.html +12 -0
- data/doc/pages/source/_includes/google_plus_one.html +2 -0
- data/doc/pages/source/_includes/menu.html +19 -0
- data/doc/pages/source/_includes/menu_brand.html +2 -0
- data/doc/pages/source/_includes/menu_right.html +1 -0
- data/doc/pages/source/_includes/post_item.html +10 -0
- data/doc/pages/source/_includes/scroll_to.html +24 -0
- data/doc/pages/source/_includes/twitter_sharing.html +9 -0
- data/doc/pages/source/_layouts/default.html +70 -0
- data/doc/pages/source/_layouts/page.html +47 -0
- data/doc/pages/source/_layouts/post.html +19 -0
- data/doc/pages/source/_layouts/presentation.html +39 -0
- data/doc/pages/source/_layouts/tag_page.html +12 -0
- data/doc/pages/source/_sass/_bootstrap-compass.scss +9 -0
- data/doc/pages/source/_sass/_bootstrap-mincer.scss +19 -0
- data/doc/pages/source/_sass/_bootstrap-sprockets.scss +9 -0
- data/doc/pages/source/_sass/_bootstrap-variables.sass +865 -0
- data/doc/pages/source/_sass/_bootstrap.scss +50 -0
- data/doc/pages/source/_sass/_specific.scss +16 -0
- data/doc/pages/source/_sass/_style.scss +172 -0
- data/doc/pages/source/_sass/bootstrap/_alerts.scss +73 -0
- data/doc/pages/source/_sass/bootstrap/_badges.scss +67 -0
- data/doc/pages/source/_sass/bootstrap/_breadcrumbs.scss +26 -0
- data/doc/pages/source/_sass/bootstrap/_button-groups.scss +243 -0
- data/doc/pages/source/_sass/bootstrap/_buttons.scss +160 -0
- data/doc/pages/source/_sass/bootstrap/_carousel.scss +269 -0
- data/doc/pages/source/_sass/bootstrap/_close.scss +36 -0
- data/doc/pages/source/_sass/bootstrap/_code.scss +69 -0
- data/doc/pages/source/_sass/bootstrap/_component-animations.scss +38 -0
- data/doc/pages/source/_sass/bootstrap/_dropdowns.scss +214 -0
- data/doc/pages/source/_sass/bootstrap/_forms.scss +570 -0
- data/doc/pages/source/_sass/bootstrap/_glyphicons.scss +301 -0
- data/doc/pages/source/_sass/bootstrap/_grid.scss +84 -0
- data/doc/pages/source/_sass/bootstrap/_input-groups.scss +166 -0
- data/doc/pages/source/_sass/bootstrap/_jumbotron.scss +50 -0
- data/doc/pages/source/_sass/bootstrap/_labels.scss +66 -0
- data/doc/pages/source/_sass/bootstrap/_list-group.scss +124 -0
- data/doc/pages/source/_sass/bootstrap/_media.scss +61 -0
- data/doc/pages/source/_sass/bootstrap/_mixins.scss +39 -0
- data/doc/pages/source/_sass/bootstrap/_modals.scss +148 -0
- data/doc/pages/source/_sass/bootstrap/_navbar.scss +663 -0
- data/doc/pages/source/_sass/bootstrap/_navs.scss +244 -0
- data/doc/pages/source/_sass/bootstrap/_normalize.scss +427 -0
- data/doc/pages/source/_sass/bootstrap/_pager.scss +54 -0
- data/doc/pages/source/_sass/bootstrap/_pagination.scss +88 -0
- data/doc/pages/source/_sass/bootstrap/_panels.scss +265 -0
- data/doc/pages/source/_sass/bootstrap/_popovers.scss +135 -0
- data/doc/pages/source/_sass/bootstrap/_print.scss +107 -0
- data/doc/pages/source/_sass/bootstrap/_progress-bars.scss +87 -0
- data/doc/pages/source/_sass/bootstrap/_responsive-embed.scss +35 -0
- data/doc/pages/source/_sass/bootstrap/_responsive-utilities.scss +177 -0
- data/doc/pages/source/_sass/bootstrap/_scaffolding.scss +150 -0
- data/doc/pages/source/_sass/bootstrap/_tables.scss +234 -0
- data/doc/pages/source/_sass/bootstrap/_theme.scss +273 -0
- data/doc/pages/source/_sass/bootstrap/_thumbnails.scss +38 -0
- data/doc/pages/source/_sass/bootstrap/_tooltip.scss +103 -0
- data/doc/pages/source/_sass/bootstrap/_type.scss +298 -0
- data/doc/pages/source/_sass/bootstrap/_utilities.scss +56 -0
- data/doc/pages/source/_sass/bootstrap/_variables.scss +862 -0
- data/doc/pages/source/_sass/bootstrap/_wells.scss +29 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_alerts.scss +14 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_background-variant.scss +11 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_border-radius.scss +18 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_buttons.scss +52 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_center-block.scss +7 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_clearfix.scss +22 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_forms.scss +88 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_gradients.scss +58 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_grid-framework.scss +81 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_grid.scss +122 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_hide-text.scss +21 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_image.scss +33 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_labels.scss +12 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_list-group.scss +31 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_nav-divider.scss +10 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_nav-vertical-align.scss +9 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_opacity.scss +8 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_pagination.scss +23 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_panels.scss +24 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_progress-bar.scss +10 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_reset-filter.scss +8 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_resize.scss +6 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_responsive-visibility.scss +21 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_size.scss +10 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_tab-focus.scss +9 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_table-row.scss +28 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_text-emphasis.scss +11 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_text-overflow.scss +8 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_vendor-prefixes.scss +222 -0
- data/doc/pages/source/atom.xml +32 -0
- data/doc/pages/source/bootstrap/config.json +429 -0
- data/doc/pages/source/bootstrap/css/bootstrap-theme.css +479 -0
- data/doc/pages/source/bootstrap/css/bootstrap-theme.min.css +10 -0
- data/doc/pages/source/bootstrap/css/bootstrap.css +6564 -0
- data/doc/pages/source/bootstrap/css/bootstrap.min.css +10 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/doc/pages/source/bootstrap/js/bootstrap.js +2309 -0
- data/doc/pages/source/bootstrap/js/bootstrap.min.js +12 -0
- data/doc/pages/source/css/app.scss +10 -0
- data/doc/pages/source/css/syntax.css +60 -0
- data/doc/pages/source/documentation/index.md +977 -0
- data/doc/pages/source/faq/index.md +16 -0
- data/doc/pages/source/images/dynflow-logos.svg +423 -0
- data/doc/pages/source/images/logo-long.png +0 -0
- data/doc/pages/source/images/logo-long.svg +116 -0
- data/doc/pages/source/images/logo-square.png +0 -0
- data/doc/pages/source/images/logo-square.svg +75 -0
- data/doc/pages/source/images/noise.png +0 -0
- data/doc/pages/source/images/screenshot.png +0 -0
- data/doc/pages/source/index.md +64 -0
- data/doc/pages/source/media/index.md +20 -0
- data/doc/pages/source/projects/index.md +32 -0
- data/dynflow.gemspec +2 -3
- data/examples/sub_plans.rb +37 -0
- data/lib/dynflow/action.rb +28 -8
- data/lib/dynflow/action/polling.rb +6 -5
- data/lib/dynflow/action/with_sub_plans.rb +162 -0
- data/lib/dynflow/execution_plan.rb +25 -7
- data/lib/dynflow/execution_plan/steps/abstract.rb +5 -2
- data/lib/dynflow/execution_plan/steps/plan_step.rb +6 -2
- data/lib/dynflow/execution_plan/steps/run_step.rb +4 -0
- data/lib/dynflow/persistence.rb +5 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +12 -2
- data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +9 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +21 -7
- data/lib/dynflow/world.rb +26 -2
- data/test/action_test.rb +107 -0
- data/test/persistance_adapters_test.rb +2 -2
- data/test/test_helper.rb +1 -1
- data/web/views/flow_step.erb +3 -0
- metadata +137 -4
|
@@ -97,6 +97,10 @@ module Dynflow
|
|
|
97
97
|
result == :error
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
def error_in_plan?
|
|
101
|
+
steps_in_state(:error).any? { |step| step.is_a? Steps::PlanStep }
|
|
102
|
+
end
|
|
103
|
+
|
|
100
104
|
def errors
|
|
101
105
|
steps.values.map(&:error).compact
|
|
102
106
|
end
|
|
@@ -105,6 +109,10 @@ module Dynflow
|
|
|
105
109
|
Type! entry_action.rescue_strategy, Action::Rescue::Strategy
|
|
106
110
|
end
|
|
107
111
|
|
|
112
|
+
def sub_plans
|
|
113
|
+
persistence.find_execution_plans(filters: { 'caller_execution_plan_id' => self.id })
|
|
114
|
+
end
|
|
115
|
+
|
|
108
116
|
def rescue_plan_id
|
|
109
117
|
case rescue_strategy
|
|
110
118
|
when Action::Rescue::Pause
|
|
@@ -141,9 +149,12 @@ module Dynflow
|
|
|
141
149
|
@last_step_id += 1
|
|
142
150
|
end
|
|
143
151
|
|
|
144
|
-
def prepare(action_class)
|
|
152
|
+
def prepare(action_class, options = {})
|
|
153
|
+
options = options.dup
|
|
154
|
+
caller_action = Type! options.delete(:caller_action), Dynflow::Action, NilClass
|
|
155
|
+
raise "Unexpected options #{options.keys.inspect}" unless options.empty?
|
|
145
156
|
save
|
|
146
|
-
@root_plan_step = add_plan_step(action_class)
|
|
157
|
+
@root_plan_step = add_plan_step(action_class, caller_action)
|
|
147
158
|
@root_plan_step.save
|
|
148
159
|
end
|
|
149
160
|
|
|
@@ -226,9 +237,13 @@ module Dynflow
|
|
|
226
237
|
current_run_flow.add_and_resolve(@dependency_graph, new_flow) if current_run_flow
|
|
227
238
|
end
|
|
228
239
|
|
|
229
|
-
def add_plan_step(action_class,
|
|
230
|
-
add_step(Steps::PlanStep, action_class, generate_action_id
|
|
231
|
-
|
|
240
|
+
def add_plan_step(action_class, caller_action = nil)
|
|
241
|
+
add_step(Steps::PlanStep, action_class, generate_action_id).tap do |step|
|
|
242
|
+
# TODO: to be removed and preferred by the caller_action
|
|
243
|
+
if caller_action && caller_action.execution_plan_id == self.id
|
|
244
|
+
@steps[caller_action.plan_step_id].children << step.id
|
|
245
|
+
end
|
|
246
|
+
step.initialize_action(caller_action)
|
|
232
247
|
end
|
|
233
248
|
end
|
|
234
249
|
|
|
@@ -312,13 +327,17 @@ module Dynflow
|
|
|
312
327
|
end
|
|
313
328
|
end
|
|
314
329
|
|
|
330
|
+
def caller_execution_plan_id
|
|
331
|
+
entry_action.caller_execution_plan_id
|
|
332
|
+
end
|
|
333
|
+
|
|
315
334
|
private
|
|
316
335
|
|
|
317
336
|
def persistence
|
|
318
337
|
world.persistence
|
|
319
338
|
end
|
|
320
339
|
|
|
321
|
-
def add_step(step_class, action_class, action_id
|
|
340
|
+
def add_step(step_class, action_class, action_id)
|
|
322
341
|
step_class.new(self.id,
|
|
323
342
|
self.generate_step_id,
|
|
324
343
|
:pending,
|
|
@@ -327,7 +346,6 @@ module Dynflow
|
|
|
327
346
|
nil,
|
|
328
347
|
world).tap do |new_step|
|
|
329
348
|
@steps[new_step.id] = new_step
|
|
330
|
-
@steps[planned_by_step_id].children << new_step.id if planned_by_step_id
|
|
331
349
|
end
|
|
332
350
|
end
|
|
333
351
|
|
|
@@ -113,8 +113,7 @@ module Dynflow
|
|
|
113
113
|
# @return [Action] in presentation mode, intended for retrieving: progress information,
|
|
114
114
|
# details, human outputs, etc.
|
|
115
115
|
def action(execution_plan)
|
|
116
|
-
|
|
117
|
-
Action.from_hash(attributes.update(phase: Action::Present, execution_plan: execution_plan), world)
|
|
116
|
+
world.persistence.load_action_for_presentation(execution_plan, action_id)
|
|
118
117
|
end
|
|
119
118
|
|
|
120
119
|
def skippable?
|
|
@@ -125,6 +124,10 @@ module Dynflow
|
|
|
125
124
|
false
|
|
126
125
|
end
|
|
127
126
|
|
|
127
|
+
def with_sub_plans?
|
|
128
|
+
false
|
|
129
|
+
end
|
|
130
|
+
|
|
128
131
|
protected
|
|
129
132
|
|
|
130
133
|
def self.new_from_hash(hash, execution_plan_id, world)
|
|
@@ -76,14 +76,18 @@ module Dynflow
|
|
|
76
76
|
hash[:children]
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def initialize_action
|
|
79
|
+
def initialize_action(caller_action)
|
|
80
80
|
attributes = { execution_plan_id: execution_plan_id,
|
|
81
81
|
id: action_id,
|
|
82
82
|
step: self,
|
|
83
83
|
plan_step_id: self.id,
|
|
84
84
|
run_step_id: nil,
|
|
85
85
|
finalize_step_id: nil,
|
|
86
|
-
phase: phase}
|
|
86
|
+
phase: phase }
|
|
87
|
+
if caller_action
|
|
88
|
+
attributes.update(caller_execution_plan_id: caller_action.execution_plan_id,
|
|
89
|
+
caller_action_id: caller_action.id)
|
|
90
|
+
end
|
|
87
91
|
@action = action_class.new(attributes, world)
|
|
88
92
|
persistence.save_action(execution_plan_id, @action)
|
|
89
93
|
@action
|
data/lib/dynflow/persistence.rb
CHANGED
|
@@ -19,6 +19,11 @@ module Dynflow
|
|
|
19
19
|
return Action.from_hash(attributes, step.world)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def load_action_for_presentation(execution_plan, action_id)
|
|
23
|
+
attributes = adapter.load_action(execution_plan.id, action_id)
|
|
24
|
+
Action.from_hash(attributes.update(phase: Action::Present, execution_plan: execution_plan), @world)
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
def save_action(execution_plan_id, action)
|
|
23
28
|
adapter.save_action(execution_plan_id, action.id, action.to_hash)
|
|
24
29
|
end
|
|
@@ -27,7 +27,7 @@ module Dynflow
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
META_DATA = { execution_plan: %w(state result started_at ended_at real_time execution_time),
|
|
30
|
-
action:
|
|
30
|
+
action: %w(caller_execution_plan_id caller_action_id),
|
|
31
31
|
step: %w(state started_at ended_at real_time execution_time action_id progress_done progress_weight) }
|
|
32
32
|
|
|
33
33
|
def initialize(config)
|
|
@@ -151,8 +151,18 @@ module Dynflow
|
|
|
151
151
|
def filter(data_set, options)
|
|
152
152
|
filters = Type! options[:filters], NilClass, Hash
|
|
153
153
|
return data_set if filters.nil?
|
|
154
|
+
unknown = filters.keys - META_DATA.fetch(:execution_plan) - %w[caller_execution_plan_id caller_action_id]
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
if filters.key?('caller_action_id') && !filters.key?('caller_execution_plan_id')
|
|
157
|
+
raise ArgumentError, "caller_action_id given but caller_execution_plan_id missing"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if filters.key?('caller_execution_plan_id')
|
|
161
|
+
data_set = data_set.join_table(:inner, TABLES[:action], :execution_plan_uuid => :uuid).
|
|
162
|
+
select_all(TABLES[:execution_plan]).distinct
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
unless (unknown).empty?
|
|
156
166
|
raise ArgumentError, "unkown columns: #{unknown.inspect}"
|
|
157
167
|
end
|
|
158
168
|
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/web_console.rb
CHANGED
|
@@ -143,7 +143,7 @@ module Dynflow
|
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def updated_url(new_params)
|
|
146
|
-
url("?" + Rack::Utils.build_nested_query(params.merge(new_params.stringify_keys)))
|
|
146
|
+
url(request.path_info + "?" + Rack::Utils.build_nested_query(params.merge(new_params.stringify_keys)))
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
def paginated_url(delta)
|
|
@@ -211,7 +211,7 @@ module Dynflow
|
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
-
def filtering_options
|
|
214
|
+
def filtering_options(show_all = false)
|
|
215
215
|
return @filtering_options if @filtering_options
|
|
216
216
|
|
|
217
217
|
if params[:filters]
|
|
@@ -223,7 +223,8 @@ module Dynflow
|
|
|
223
223
|
|
|
224
224
|
filters = params[:filters]
|
|
225
225
|
elsif supported_filter?('state')
|
|
226
|
-
|
|
226
|
+
excluded_states = show_all ? [] : ['stopped']
|
|
227
|
+
filters = { 'state' => ExecutionPlan.states.map(&:to_s) - excluded_states }
|
|
227
228
|
else
|
|
228
229
|
filters = {}
|
|
229
230
|
end
|
|
@@ -231,6 +232,13 @@ module Dynflow
|
|
|
231
232
|
return @filtering_options
|
|
232
233
|
end
|
|
233
234
|
|
|
235
|
+
def find_execution_plans_options(show_all = false)
|
|
236
|
+
options = HashWithIndifferentAccess.new
|
|
237
|
+
options.merge!(filtering_options(show_all))
|
|
238
|
+
options.merge!(pagination_options)
|
|
239
|
+
options.merge!(ordering_options)
|
|
240
|
+
end
|
|
241
|
+
|
|
234
242
|
def filter_checkbox(field, values)
|
|
235
243
|
out = "<p>#{field}: %s</p>"
|
|
236
244
|
checkboxes = values.map do |value|
|
|
@@ -245,15 +253,21 @@ module Dynflow
|
|
|
245
253
|
end
|
|
246
254
|
|
|
247
255
|
get('/') do
|
|
248
|
-
options =
|
|
249
|
-
options.merge!(filtering_options)
|
|
250
|
-
options.merge!(pagination_options)
|
|
251
|
-
options.merge!(ordering_options)
|
|
256
|
+
options = find_execution_plans_options
|
|
252
257
|
|
|
253
258
|
@plans = world.persistence.find_execution_plans(options)
|
|
254
259
|
erb :index
|
|
255
260
|
end
|
|
256
261
|
|
|
262
|
+
get('/:execution_plan_id/actions/:action_id/sub_plans') do |execution_plan_id, action_id|
|
|
263
|
+
options = find_execution_plans_options(true)
|
|
264
|
+
options[:filters].update('caller_execution_plan_id' => execution_plan_id,
|
|
265
|
+
'caller_action_id' => action_id)
|
|
266
|
+
@plans = world.persistence.find_execution_plans(options)
|
|
267
|
+
erb :index
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
|
|
257
271
|
get('/:id') do |id|
|
|
258
272
|
@plan = world.persistence.load_execution_plan(id)
|
|
259
273
|
@notice = params[:notice]
|
data/lib/dynflow/world.rb
CHANGED
|
@@ -91,8 +91,14 @@ module Dynflow
|
|
|
91
91
|
|
|
92
92
|
# @return [TriggerResult]
|
|
93
93
|
# blocks until action_class is planned
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
# if no arguments given, the plan is expected to be returned by a block
|
|
95
|
+
def trigger(action_class = nil, *args, &block)
|
|
96
|
+
if action_class.nil?
|
|
97
|
+
raise 'Neither action_class nor a block given' if block.nil?
|
|
98
|
+
execution_plan = block.call(self)
|
|
99
|
+
else
|
|
100
|
+
execution_plan = plan(action_class, *args)
|
|
101
|
+
end
|
|
96
102
|
planned = execution_plan.state == :planned
|
|
97
103
|
|
|
98
104
|
if planned
|
|
@@ -107,7 +113,14 @@ module Dynflow
|
|
|
107
113
|
end
|
|
108
114
|
|
|
109
115
|
def event(execution_plan_id, step_id, event, future = Future.new)
|
|
116
|
+
# we do this to avoid unresolved future when getting into
|
|
117
|
+
# the executor mailbox right at the termination.
|
|
118
|
+
# TODO: concurrent-ruby dead letter routing should make this
|
|
119
|
+
# more elegant
|
|
120
|
+
raise Dynflow::Error, "terminating world is not accepting events" if terminating?
|
|
110
121
|
executor.event execution_plan_id, step_id, event, future
|
|
122
|
+
rescue => e
|
|
123
|
+
future.fail e
|
|
111
124
|
end
|
|
112
125
|
|
|
113
126
|
def plan(action_class, *args)
|
|
@@ -117,6 +130,13 @@ module Dynflow
|
|
|
117
130
|
end
|
|
118
131
|
end
|
|
119
132
|
|
|
133
|
+
def plan_with_caller(caller_action, action_class, *args)
|
|
134
|
+
ExecutionPlan.new(self).tap do |execution_plan|
|
|
135
|
+
execution_plan.prepare(action_class, caller_action: caller_action)
|
|
136
|
+
execution_plan.plan(*args)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
120
140
|
# @return [Future] containing execution_plan when finished
|
|
121
141
|
# raises when ExecutionPlan is not accepted for execution
|
|
122
142
|
def execute(execution_plan_id, finished = Future.new)
|
|
@@ -138,6 +158,10 @@ module Dynflow
|
|
|
138
158
|
Future.join([@executor_terminated, @clock_terminated], future)
|
|
139
159
|
end
|
|
140
160
|
|
|
161
|
+
def terminating?
|
|
162
|
+
!!@executor_terminated
|
|
163
|
+
end
|
|
164
|
+
|
|
141
165
|
# Detects execution plans that are marked as running but no executor
|
|
142
166
|
# handles them (probably result of non-standard executor termination)
|
|
143
167
|
#
|
data/test/action_test.rb
CHANGED
|
@@ -252,5 +252,112 @@ module Dynflow
|
|
|
252
252
|
end
|
|
253
253
|
|
|
254
254
|
end
|
|
255
|
+
|
|
256
|
+
describe Action::WithSubPlans do
|
|
257
|
+
|
|
258
|
+
class FailureSimulator
|
|
259
|
+
class << self
|
|
260
|
+
attr_accessor :fail_in_child_plan, :fail_in_child_run
|
|
261
|
+
|
|
262
|
+
def reset!
|
|
263
|
+
self.fail_in_child_plan = self.fail_in_child_run = false
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
class ParentAction < Dynflow::Action
|
|
269
|
+
|
|
270
|
+
include Dynflow::Action::WithSubPlans
|
|
271
|
+
|
|
272
|
+
def create_sub_plans
|
|
273
|
+
input[:count].times.map{ trigger(ChildAction) }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def resume
|
|
277
|
+
output[:custom_resume] = true
|
|
278
|
+
super
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
class ChildAction < Dynflow::Action
|
|
283
|
+
def plan
|
|
284
|
+
if FailureSimulator.fail_in_child_plan
|
|
285
|
+
raise "Fail in child plan"
|
|
286
|
+
end
|
|
287
|
+
super
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def run
|
|
291
|
+
if FailureSimulator.fail_in_child_run
|
|
292
|
+
raise "Fail in child run"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
let(:execution_plan) { world.trigger(ParentAction, count: 2).finished.value }
|
|
298
|
+
|
|
299
|
+
before do
|
|
300
|
+
FailureSimulator.reset!
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
specify "the sub-plan stores the information about its parent" do
|
|
304
|
+
sub_plans = execution_plan.sub_plans
|
|
305
|
+
sub_plans.size.must_equal 2
|
|
306
|
+
sub_plans.each { |sub_plan| sub_plan.caller_execution_plan_id.must_equal execution_plan.id }
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
specify "it saves the information about number for sub plans in the output" do
|
|
310
|
+
execution_plan.entry_action.output.must_equal('total_count' => 2,
|
|
311
|
+
'failed_count' => 0,
|
|
312
|
+
'success_count' => 2)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
specify "when a sub plan fails, the caller action fails as well" do
|
|
316
|
+
FailureSimulator.fail_in_child_run = true
|
|
317
|
+
execution_plan.entry_action.output.must_equal('total_count' => 2,
|
|
318
|
+
'failed_count' => 2,
|
|
319
|
+
'success_count' => 0)
|
|
320
|
+
execution_plan.state.must_equal :paused
|
|
321
|
+
execution_plan.result.must_equal :error
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
describe 'resuming' do
|
|
325
|
+
specify "resuming the action depends on the resume method definition" do
|
|
326
|
+
FailureSimulator.fail_in_child_plan = true
|
|
327
|
+
execution_plan.state.must_equal :paused
|
|
328
|
+
FailureSimulator.fail_in_child_plan = false
|
|
329
|
+
resumed_plan = world.execute(execution_plan.id).value
|
|
330
|
+
resumed_plan.entry_action.output[:custom_resume].must_equal true
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
specify "by default, when no sub plans were planned successfully, it call create_sub_plans again" do
|
|
334
|
+
FailureSimulator.fail_in_child_plan = true
|
|
335
|
+
execution_plan.state.must_equal :paused
|
|
336
|
+
FailureSimulator.fail_in_child_plan = false
|
|
337
|
+
resumed_plan = world.execute(execution_plan.id).value
|
|
338
|
+
resumed_plan.state.must_equal :stopped
|
|
339
|
+
resumed_plan.result.must_equal :success
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
specify "by default, when any sub-plan was planned, it succeeds only when the sub-plans were already finished" do
|
|
343
|
+
FailureSimulator.fail_in_child_run = true
|
|
344
|
+
execution_plan.state.must_equal :paused
|
|
345
|
+
sub_plans = execution_plan.sub_plans
|
|
346
|
+
|
|
347
|
+
FailureSimulator.fail_in_child_run = false
|
|
348
|
+
resumed_plan = world.execute(execution_plan.id).value
|
|
349
|
+
resumed_plan.state.must_equal :paused
|
|
350
|
+
|
|
351
|
+
world.execute(sub_plans.first.id).wait
|
|
352
|
+
resumed_plan = world.execute(execution_plan.id).value
|
|
353
|
+
resumed_plan.state.must_equal :paused
|
|
354
|
+
|
|
355
|
+
sub_plans.drop(1).each { |sub_plan| world.execute(sub_plan.id).wait }
|
|
356
|
+
resumed_plan = world.execute(execution_plan.id).value
|
|
357
|
+
resumed_plan.state.must_equal :stopped
|
|
358
|
+
resumed_plan.result.must_equal :success
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
255
362
|
end
|
|
256
363
|
end
|
|
@@ -87,13 +87,13 @@ module PersistenceAdapterTest
|
|
|
87
87
|
real_time: 0.0, execution_time: 0.0 }
|
|
88
88
|
storage.save_execution_plan('plan1', plan)
|
|
89
89
|
|
|
90
|
-
action = { id: 1 }
|
|
90
|
+
action = { id: 1, caller_execution_plan_id: nil, caller_action_id: nil }
|
|
91
91
|
-> { storage.load_action('plan1', 1) }.must_raise KeyError
|
|
92
92
|
|
|
93
93
|
storage.save_action('plan1', 1, action)
|
|
94
94
|
storage.load_action('plan1', 1)[:id].must_equal 1
|
|
95
95
|
storage.load_action('plan1', 1)['id'].must_equal 1
|
|
96
|
-
storage.load_action('plan1', 1).keys.
|
|
96
|
+
storage.load_action('plan1', 1).keys.must_equal %w[id caller_execution_plan_id caller_action_id]
|
|
97
97
|
|
|
98
98
|
storage.save_action('plan1', 1, nil)
|
|
99
99
|
-> { storage.load_action('plan1', 1) }.must_raise KeyError
|