dynflow 0.1.0 → 0.2.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.
- 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
|