dynflow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +0 -10
- data/MIT-LICENSE +1 -1
- data/README.md +99 -37
- data/Rakefile +2 -6
- data/doc/images/logo.png +0 -0
- data/dynflow.gemspec +10 -1
- data/examples/generate_work_for_daemon.rb +24 -0
- data/examples/orchestrate.rb +121 -0
- data/examples/run_daemon.rb +17 -0
- data/examples/web_console.rb +29 -0
- data/lib/dynflow.rb +27 -6
- data/lib/dynflow/action.rb +185 -77
- data/lib/dynflow/action/cancellable_polling.rb +18 -0
- data/lib/dynflow/action/finalize_phase.rb +18 -0
- data/lib/dynflow/action/flow_phase.rb +44 -0
- data/lib/dynflow/action/format.rb +46 -0
- data/lib/dynflow/action/missing.rb +26 -0
- data/lib/dynflow/action/plan_phase.rb +85 -0
- data/lib/dynflow/action/polling.rb +49 -0
- data/lib/dynflow/action/presenter.rb +51 -0
- data/lib/dynflow/action/progress.rb +62 -0
- data/lib/dynflow/action/run_phase.rb +43 -0
- data/lib/dynflow/action/suspended.rb +21 -0
- data/lib/dynflow/clock.rb +133 -0
- data/lib/dynflow/daemon.rb +29 -0
- data/lib/dynflow/execution_plan.rb +285 -33
- data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
- data/lib/dynflow/execution_plan/output_reference.rb +52 -0
- data/lib/dynflow/execution_plan/steps.rb +12 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
- data/lib/dynflow/execution_plan/steps/error.rb +33 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
- data/lib/dynflow/executors.rb +9 -0
- data/lib/dynflow/executors/abstract.rb +32 -0
- data/lib/dynflow/executors/parallel.rb +88 -0
- data/lib/dynflow/executors/parallel/core.rb +119 -0
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
- data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
- data/lib/dynflow/executors/parallel/pool.rb +102 -0
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
- data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
- data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
- data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
- data/lib/dynflow/executors/parallel/worker.rb +30 -0
- data/lib/dynflow/executors/remote_via_socket.rb +38 -0
- data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
- data/lib/dynflow/flows.rb +13 -0
- data/lib/dynflow/flows/abstract.rb +36 -0
- data/lib/dynflow/flows/abstract_composed.rb +104 -0
- data/lib/dynflow/flows/atom.rb +36 -0
- data/lib/dynflow/flows/concurrence.rb +28 -0
- data/lib/dynflow/flows/sequence.rb +13 -0
- data/lib/dynflow/future.rb +173 -0
- data/lib/dynflow/listeners.rb +7 -0
- data/lib/dynflow/listeners/abstract.rb +13 -0
- data/lib/dynflow/listeners/serialization.rb +41 -0
- data/lib/dynflow/listeners/socket.rb +88 -0
- data/lib/dynflow/logger_adapters.rb +8 -0
- data/lib/dynflow/logger_adapters/abstract.rb +30 -0
- data/lib/dynflow/logger_adapters/delegator.rb +13 -0
- data/lib/dynflow/logger_adapters/formatters.rb +8 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
- data/lib/dynflow/logger_adapters/simple.rb +59 -0
- data/lib/dynflow/micro_actor.rb +102 -0
- data/lib/dynflow/persistence.rb +53 -0
- data/lib/dynflow/persistence_adapters.rb +6 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
- data/lib/dynflow/serializable.rb +66 -0
- data/lib/dynflow/simple_world.rb +18 -0
- data/lib/dynflow/stateful.rb +40 -0
- data/lib/dynflow/testing.rb +32 -0
- data/lib/dynflow/testing/assertions.rb +64 -0
- data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
- data/lib/dynflow/testing/dummy_executor.rb +29 -0
- data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
- data/lib/dynflow/testing/dummy_step.rb +19 -0
- data/lib/dynflow/testing/dummy_world.rb +33 -0
- data/lib/dynflow/testing/factories.rb +83 -0
- data/lib/dynflow/testing/managed_clock.rb +23 -0
- data/lib/dynflow/testing/mimic.rb +38 -0
- data/lib/dynflow/transaction_adapters.rb +9 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
- data/lib/dynflow/transaction_adapters/none.rb +12 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +277 -0
- data/lib/dynflow/world.rb +168 -0
- data/test/action_test.rb +89 -11
- data/test/clock_test.rb +59 -0
- data/test/code_workflow_example.rb +382 -0
- data/test/execution_plan_test.rb +195 -64
- data/test/executor_test.rb +692 -0
- data/test/persistance_adapters_test.rb +173 -0
- data/test/test_helper.rb +316 -1
- data/test/testing_test.rb +148 -0
- data/test/web_console_test.rb +38 -0
- data/web/assets/javascripts/application.js +25 -0
- data/web/assets/stylesheets/application.css +101 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
- data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
- data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
- data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
- data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
- data/web/assets/vendor/jquery/jquery.js +9807 -0
- data/web/views/flow.erb +19 -0
- data/web/views/flow_step.erb +31 -0
- data/web/views/index.erb +39 -0
- data/web/views/layout.erb +20 -0
- data/web/views/plan_step.erb +11 -0
- data/web/views/show.erb +54 -0
- metadata +250 -11
- data/examples/events.rb +0 -71
- data/examples/workflow.rb +0 -140
- data/lib/dynflow/bus.rb +0 -168
- data/lib/dynflow/dispatcher.rb +0 -36
- data/lib/dynflow/logger.rb +0 -34
- data/lib/dynflow/step.rb +0 -234
- data/test/bus_test.rb +0 -150
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class Daemon
|
3
|
+
include Algebrick::TypeCheck
|
4
|
+
|
5
|
+
def initialize(listener, world, lock_file = nil)
|
6
|
+
@listener = Type! listener, Listeners::Abstract
|
7
|
+
@world = Type! world, World
|
8
|
+
@lock_file = Type! lock_file, String, NilClass
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
with_lock_file do
|
13
|
+
terminated = Future.new
|
14
|
+
trap('SIGINT') { @world.terminate terminated }
|
15
|
+
terminated.wait
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_lock_file(&block)
|
20
|
+
if @lock_file
|
21
|
+
raise "Lockfile #{@lock_file} is already present." if File.exist?(@lock_file)
|
22
|
+
File.write(@lock_file, "Locked at #{Time.now} by process #{$$}\n")
|
23
|
+
end
|
24
|
+
block.call
|
25
|
+
ensure
|
26
|
+
File.delete(@lock_file) if @lock_file
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,56 +1,308 @@
|
|
1
|
-
require '
|
1
|
+
require 'uuidtools'
|
2
2
|
|
3
3
|
module Dynflow
|
4
|
-
class ExecutionPlan
|
5
4
|
|
6
|
-
|
5
|
+
# TODO extract planning logic to an extra class ExecutionPlanner
|
6
|
+
class ExecutionPlan < Serializable
|
7
|
+
include Algebrick::TypeCheck
|
8
|
+
include Stateful
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# one of [new, running, paused, aborted, finished]
|
12
|
-
attr_accessor :status
|
10
|
+
require 'dynflow/execution_plan/steps'
|
11
|
+
require 'dynflow/execution_plan/output_reference'
|
12
|
+
require 'dynflow/execution_plan/dependency_graph'
|
13
13
|
|
14
|
-
|
14
|
+
attr_reader :id, :world, :root_plan_step, :steps, :run_flow, :finalize_flow,
|
15
|
+
:started_at, :ended_at, :execution_time, :real_time
|
15
16
|
|
16
|
-
def
|
17
|
-
@
|
18
|
-
@run_steps = run_steps
|
19
|
-
@finalize_steps = finalize_steps
|
20
|
-
@status = 'new'
|
17
|
+
def self.states
|
18
|
+
@states ||= [:pending, :planning, :planned, :running, :paused, :stopped]
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
24
|
-
|
21
|
+
def self.state_transitions
|
22
|
+
@state_transitions ||= { pending: [:planning],
|
23
|
+
planning: [:planned, :stopped],
|
24
|
+
planned: [:running],
|
25
|
+
running: [:paused, :stopped],
|
26
|
+
paused: [:running],
|
27
|
+
stopped: [] }
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
# all params with default values are part of *private* api
|
31
|
+
def initialize(world,
|
32
|
+
id = UUIDTools::UUID.random_create.to_s,
|
33
|
+
state = :pending,
|
34
|
+
root_plan_step = nil,
|
35
|
+
run_flow = Flows::Concurrence.new([]),
|
36
|
+
finalize_flow = Flows::Sequence.new([]),
|
37
|
+
steps = {},
|
38
|
+
started_at = nil,
|
39
|
+
ended_at = nil,
|
40
|
+
execution_time = 0.0,
|
41
|
+
real_time = 0.0)
|
42
|
+
|
43
|
+
@id = Type! id, String
|
44
|
+
@world = Type! world, World
|
45
|
+
self.state = state
|
46
|
+
@run_flow = Type! run_flow, Flows::Abstract
|
47
|
+
@finalize_flow = Type! finalize_flow, Flows::Abstract
|
48
|
+
@root_plan_step = root_plan_step
|
49
|
+
@started_at = Type! started_at, Time, NilClass
|
50
|
+
@ended_at = Type! ended_at, Time, NilClass
|
51
|
+
@execution_time = Type! execution_time, Float
|
52
|
+
@real_time = Type! real_time, Float
|
53
|
+
|
54
|
+
steps.all? do |k, v|
|
55
|
+
Type! k, Integer
|
56
|
+
Type! v, Steps::Abstract
|
57
|
+
end
|
58
|
+
@steps = steps
|
59
|
+
end
|
60
|
+
|
61
|
+
def logger
|
62
|
+
@world.logger
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_state(state)
|
66
|
+
original = self.state
|
67
|
+
case self.state = state
|
68
|
+
when :planning
|
69
|
+
@started_at = Time.now
|
70
|
+
when :stopped
|
71
|
+
@ended_at = Time.now
|
72
|
+
@real_time = @ended_at - @started_at
|
73
|
+
else
|
74
|
+
# ignore
|
75
|
+
end
|
76
|
+
logger.debug "execution plan #{id} #{original} >> #{state}"
|
77
|
+
self.save
|
78
|
+
end
|
79
|
+
|
80
|
+
def update_execution_time(execution_time)
|
81
|
+
@execution_time += execution_time
|
82
|
+
end
|
83
|
+
|
84
|
+
def result
|
85
|
+
all_steps = steps.values
|
86
|
+
if all_steps.any? { |step| step.state == :error }
|
87
|
+
return :error
|
88
|
+
elsif all_steps.all? { |step| [:success, :skipped].include?(step.state) }
|
89
|
+
return :success
|
90
|
+
else
|
91
|
+
return :pending
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def error?
|
96
|
+
result == :error
|
97
|
+
end
|
98
|
+
|
99
|
+
def generate_action_id
|
100
|
+
@last_action_id ||= 0
|
101
|
+
@last_action_id += 1
|
29
102
|
end
|
30
103
|
|
31
|
-
def
|
32
|
-
|
33
|
-
@
|
34
|
-
@finalize_steps << Step::Finalize.new(run_step) if action.respond_to? :finalize
|
104
|
+
def generate_step_id
|
105
|
+
@last_step_id ||= 0
|
106
|
+
@last_step_id += 1
|
35
107
|
end
|
36
108
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
self.finalize_steps.concat(other.finalize_steps)
|
41
|
-
self.status = other.status
|
109
|
+
def prepare(action_class)
|
110
|
+
save
|
111
|
+
@root_plan_step = add_step(Steps::PlanStep, action_class, generate_action_id)
|
42
112
|
end
|
43
113
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
114
|
+
def plan(*args)
|
115
|
+
update_state(:planning)
|
116
|
+
world.transaction_adapter.transaction do
|
117
|
+
with_planning_scope do
|
118
|
+
root_plan_step.execute(self, nil, *args)
|
48
119
|
|
49
|
-
|
50
|
-
|
120
|
+
if @dependency_graph.unresolved?
|
121
|
+
raise "Some dependencies were not resolved: #{@dependency_graph.inspect}"
|
122
|
+
end
|
51
123
|
end
|
124
|
+
|
125
|
+
if @run_flow.size == 1
|
126
|
+
@run_flow = @run_flow.sub_flows.first
|
127
|
+
end
|
128
|
+
|
129
|
+
world.transaction_adapter.rollback if error?
|
130
|
+
end
|
131
|
+
steps.values.each(&:save)
|
132
|
+
update_state(error? ? :stopped : :planned)
|
133
|
+
end
|
134
|
+
|
135
|
+
def skip(step)
|
136
|
+
raise "plan step can't be skipped" if step.is_a? Steps::PlanStep
|
137
|
+
steps_to_skip = steps_to_skip(step).each do |s|
|
138
|
+
s.state = :skipped
|
139
|
+
s.save
|
140
|
+
end
|
141
|
+
self.save
|
142
|
+
return steps_to_skip
|
143
|
+
end
|
144
|
+
|
145
|
+
# All the steps that need to get skipped when wanting to skip the step
|
146
|
+
# includes the step itself, all steps dependent on it (even transitively)
|
147
|
+
# FIND maybe move to persistence to let adapter to do it effectively?
|
148
|
+
# @return [Array<Steps::Abstract>]
|
149
|
+
def steps_to_skip(step)
|
150
|
+
dependent_steps = @steps.values.find_all do |s|
|
151
|
+
next if s.is_a? Steps::PlanStep
|
152
|
+
action = persistence.load_action(s)
|
153
|
+
action.required_step_ids.include?(step.id)
|
154
|
+
end
|
155
|
+
|
156
|
+
steps_to_skip = dependent_steps.map do |dependent_step|
|
157
|
+
steps_to_skip(dependent_step)
|
158
|
+
end.flatten
|
159
|
+
|
160
|
+
steps_to_skip << step
|
161
|
+
|
162
|
+
if step.is_a? Steps::RunStep
|
163
|
+
finalize_step_id = persistence.load_action(step).finalize_step_id
|
164
|
+
steps_to_skip << steps[finalize_step_id] if finalize_step_id
|
165
|
+
end
|
166
|
+
|
167
|
+
return steps_to_skip.uniq
|
168
|
+
end
|
169
|
+
|
170
|
+
# @api private
|
171
|
+
def current_run_flow
|
172
|
+
@run_flow_stack.last
|
173
|
+
end
|
174
|
+
|
175
|
+
# @api private
|
176
|
+
def with_planning_scope(&block)
|
177
|
+
@run_flow_stack = []
|
178
|
+
@dependency_graph = DependencyGraph.new
|
179
|
+
switch_flow(run_flow, &block)
|
180
|
+
ensure
|
181
|
+
@run_flow_stack = nil
|
182
|
+
@dependency_graph = nil
|
183
|
+
end
|
184
|
+
|
185
|
+
# @api private
|
186
|
+
# Switches the flow type (Sequence, Concurrence) to be used within the block.
|
187
|
+
def switch_flow(new_flow, &block)
|
188
|
+
@run_flow_stack << new_flow
|
189
|
+
return block.call
|
190
|
+
ensure
|
191
|
+
@run_flow_stack.pop
|
192
|
+
current_run_flow.add_and_resolve(@dependency_graph, new_flow) if current_run_flow
|
193
|
+
end
|
194
|
+
|
195
|
+
def add_plan_step(action_class, planned_by)
|
196
|
+
add_step(Steps::PlanStep, action_class, generate_action_id, planned_by.plan_step_id)
|
197
|
+
end
|
198
|
+
|
199
|
+
def add_run_step(action)
|
200
|
+
add_step(Steps::RunStep, action.action_class, action.id).tap do |step|
|
201
|
+
@dependency_graph.add_dependencies(step, action)
|
202
|
+
current_run_flow.add_and_resolve(@dependency_graph, Flows::Atom.new(step.id))
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def add_finalize_step(action)
|
207
|
+
add_step(Steps::FinalizeStep, action.action_class, action.id).tap do |step|
|
208
|
+
finalize_flow << Flows::Atom.new(step.id)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_hash
|
213
|
+
recursive_to_hash id: self.id,
|
214
|
+
class: self.class.to_s,
|
215
|
+
state: self.state,
|
216
|
+
result: result,
|
217
|
+
root_plan_step_id: root_plan_step && root_plan_step.id,
|
218
|
+
run_flow: run_flow,
|
219
|
+
finalize_flow: finalize_flow,
|
220
|
+
step_ids: steps.map { |id, _| id },
|
221
|
+
started_at: time_to_str(started_at),
|
222
|
+
ended_at: time_to_str(ended_at),
|
223
|
+
execution_time: execution_time,
|
224
|
+
real_time: real_time
|
225
|
+
end
|
226
|
+
|
227
|
+
def save
|
228
|
+
persistence.save_execution_plan(self)
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.new_from_hash(hash, world)
|
232
|
+
check_class_matching hash
|
233
|
+
execution_plan_id = hash[:id]
|
234
|
+
steps = steps_from_hash(hash[:step_ids], execution_plan_id, world)
|
235
|
+
self.new(world,
|
236
|
+
execution_plan_id,
|
237
|
+
hash[:state],
|
238
|
+
steps[hash[:root_plan_step_id]],
|
239
|
+
Flows::Abstract.from_hash(hash[:run_flow]),
|
240
|
+
Flows::Abstract.from_hash(hash[:finalize_flow]),
|
241
|
+
steps,
|
242
|
+
string_to_time(hash[:started_at]),
|
243
|
+
string_to_time(hash[:ended_at]),
|
244
|
+
hash[:execution_time],
|
245
|
+
hash[:real_time])
|
246
|
+
end
|
247
|
+
|
248
|
+
# @return [0..1] the percentage of the progress. See Action::Progress for more
|
249
|
+
# info
|
250
|
+
def progress
|
251
|
+
flow_step_ids = run_flow.all_step_ids + finalize_flow.all_step_ids
|
252
|
+
plan_done, plan_total = flow_step_ids.reduce([0.0, 0]) do |(done, total), step_id|
|
253
|
+
step_progress_done, step_progress_weight = self.steps[step_id].progress
|
254
|
+
[done + (step_progress_done * step_progress_weight),
|
255
|
+
total + step_progress_weight]
|
256
|
+
end
|
257
|
+
plan_total > 0 ? (plan_done / plan_total) : 1
|
258
|
+
end
|
259
|
+
|
260
|
+
# This method can be used to access result of the whole execution plan and detailed
|
261
|
+
# progress.
|
262
|
+
# @return [Array<Action::Presenter>] presenter of the actions
|
263
|
+
# involved in the plan
|
264
|
+
def actions
|
265
|
+
action_steps = Hash.new { |h, k| h[k] = [] }
|
266
|
+
all_actions = []
|
267
|
+
steps.values.each do |step|
|
268
|
+
action_steps[step.action_id] << step
|
269
|
+
end
|
270
|
+
action_steps.each do |action_id, involved_steps|
|
271
|
+
action = Action::Presenter.load(self,
|
272
|
+
action_id,
|
273
|
+
involved_steps,
|
274
|
+
all_actions)
|
275
|
+
all_actions << action
|
276
|
+
end
|
277
|
+
return all_actions
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
281
|
+
|
282
|
+
def persistence
|
283
|
+
world.persistence
|
284
|
+
end
|
285
|
+
|
286
|
+
def add_step(step_class, action_class, action_id, planned_by_step_id = nil)
|
287
|
+
step_class.new(self.id,
|
288
|
+
self.generate_step_id,
|
289
|
+
:pending,
|
290
|
+
action_class,
|
291
|
+
action_id,
|
292
|
+
nil,
|
293
|
+
world).tap do |new_step|
|
294
|
+
@steps[new_step.id] = new_step
|
295
|
+
@steps[planned_by_step_id].children << new_step.id if planned_by_step_id
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.steps_from_hash(step_ids, execution_plan_id, world)
|
300
|
+
step_ids.inject({}) do |hash, step_id|
|
301
|
+
step = world.persistence.load_step(execution_plan_id, step_id, world)
|
302
|
+
hash.update(step_id.to_i => step)
|
52
303
|
end
|
53
304
|
end
|
54
305
|
|
306
|
+
private_class_method :steps_from_hash
|
55
307
|
end
|
56
308
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class ExecutionPlan::DependencyGraph
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@graph = Hash.new { |h, k| h[k] = Set.new }
|
6
|
+
end
|
7
|
+
|
8
|
+
# adds dependencies to graph that +step+ has based
|
9
|
+
# on the steps referenced in its +input+
|
10
|
+
def add_dependencies(step, action)
|
11
|
+
action.required_step_ids.each do |required_step_id|
|
12
|
+
@graph[step.id] << required_step_id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def required_step_ids(step_id)
|
17
|
+
@graph[step_id]
|
18
|
+
end
|
19
|
+
|
20
|
+
def mark_satisfied(step_id, required_step_id)
|
21
|
+
@graph[step_id].delete(required_step_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unresolved?
|
25
|
+
@graph.any? { |step_id, required_step_ids| required_step_ids.any? }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class ExecutionPlan::OutputReference < Serializable
|
3
|
+
|
4
|
+
attr_reader :step_id, :action_id, :subkeys
|
5
|
+
|
6
|
+
def initialize(step_id, action_id, subkeys = [])
|
7
|
+
@step_id = step_id
|
8
|
+
@action_id = action_id
|
9
|
+
@subkeys = subkeys
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](subkey)
|
13
|
+
return self.class.new(step_id, action_id, subkeys.dup << subkey)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
recursive_to_hash class: self.class.to_s,
|
18
|
+
step_id: step_id,
|
19
|
+
action_id: action_id,
|
20
|
+
subkeys: subkeys
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Step(#{@step_id}).output".tap do |ret|
|
25
|
+
ret << @subkeys.map { |k| "[:#{k}]" }.join('') if @subkeys.any?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :inspect, :to_s
|
30
|
+
|
31
|
+
def dereference(persistence, execution_plan_id)
|
32
|
+
action_data = persistence.adapter.load_action(execution_plan_id, action_id)
|
33
|
+
deref = action_data[:output]
|
34
|
+
@subkeys.each do |subkey|
|
35
|
+
if deref.respond_to?(:[])
|
36
|
+
deref = deref[subkey]
|
37
|
+
else
|
38
|
+
raise "We were not able to dereference subkey #{@subkeys} from #{self.inspect}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
return deref
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def self.new_from_hash(hash)
|
47
|
+
check_class_matching hash
|
48
|
+
new(hash[:step_id], hash[:action_id], hash[:subkeys])
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|