dynflow 0.8.1 → 0.8.2
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.
- checksums.yaml +8 -8
- data/doc/pages/source/documentation/index.md +47 -12
- data/dynflow.gemspec +2 -2
- data/examples/future_execution.rb +73 -0
- data/lib/dynflow.rb +4 -1
- data/lib/dynflow/action.rb +15 -0
- data/lib/dynflow/config.rb +15 -1
- data/lib/dynflow/coordinator.rb +7 -0
- data/lib/dynflow/execution_plan.rb +15 -3
- data/lib/dynflow/execution_plan/steps/plan_step.rb +5 -1
- data/lib/dynflow/middleware.rb +4 -0
- data/lib/dynflow/middleware/stack.rb +1 -1
- data/lib/dynflow/middleware/world.rb +1 -1
- data/lib/dynflow/persistence.rb +19 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +16 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +31 -3
- data/lib/dynflow/persistence_adapters/sequel_migrations/006_fix_data_length.rb +17 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/007_future_execution.rb +13 -0
- data/lib/dynflow/scheduled_plan.rb +65 -0
- data/lib/dynflow/schedulers.rb +9 -0
- data/lib/dynflow/schedulers/abstract.rb +37 -0
- data/lib/dynflow/schedulers/abstract_core.rb +65 -0
- data/lib/dynflow/schedulers/polling.rb +32 -0
- data/lib/dynflow/serializers.rb +8 -0
- data/lib/dynflow/serializers/abstract.rb +15 -0
- data/lib/dynflow/serializers/noop.rb +15 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web/console.rb +8 -23
- data/lib/dynflow/web/console_helpers.rb +10 -0
- data/lib/dynflow/world.rb +99 -24
- data/test/abnormal_states_recovery_test.rb +64 -0
- data/test/future_execution_test.rb +114 -0
- data/test/middleware_test.rb +8 -2
- data/test/support/middleware_example.rb +11 -0
- data/test/test_helper.rb +1 -0
- data/web/views/show.erb +11 -0
- data/web/views/worlds.erb +19 -3
- metadata +19 -6
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MGY3ZTU0Nzk1ZTJlMjQ3YzRjMTI4ZjkzYTNkODZhYWIzYzZhYzc4ZQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NTU1MDY2YTBhNjk2Mjc2YjJkNDhkM2JhYzFjYzk1ODU3ZDUzNDc2OQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDRiZmQ2ODQ1MjEyYThmMDg4YjkyNDE1Y2Q5MTI1OWNkMDNjODlmODE5ODlk
|
10
|
+
N2Q2NDQyNzYyNDQzOWFmZGRhMjZmYTEwYWM1YTkyZTdlMDExMWY0NmU4N2Zl
|
11
|
+
MzkxYjFlZDgxY2I1MWQ4ZjhhZTgxZDY4NTI3NmRkMTBhNDE5NzE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MGQyNzY3Y2QwZTZhYTQwNGIwYzY4NzgyNzg3NjVhZTliMDBmZTNjMzBkNDc0
|
14
|
+
ZTRhMGZhNGJiODEwZjIzODNlMDczMmU0ZDNlNDIwYzQyZmJkMmUzNzhjNDQz
|
15
|
+
MDRmMzMxNzdmMWZhNWRlOWE4Yjk1Nzc5Njc2MjAyMmUwNzBhMzU=
|
@@ -123,7 +123,7 @@ end
|
|
123
123
|
```
|
124
124
|
|
125
125
|
Note that it does not have to be only other actions that are planned to run.
|
126
|
-
In fact it's very common that the action
|
126
|
+
In fact it's very common that the action plans itself, which means it will
|
127
127
|
put its own `run` method call in the execution plan. In order to do that
|
128
128
|
you can use `plan_self`. This could be used in MyActions::File::Destroy
|
129
129
|
used in previous example
|
@@ -142,7 +142,7 @@ end
|
|
142
142
|
|
143
143
|
In example above, it seems that `plan_self` is just shortcut to
|
144
144
|
`plan_action MyActions::File::Destroy, filename` but it's not entirely true.
|
145
|
-
Note that `plan_action` always
|
145
|
+
Note that `plan_action` always triggers `plan` of a given action while `plan_self`
|
146
146
|
plans only the `run` of Action, so by using `plan_action` we'd end up in
|
147
147
|
endless loop.
|
148
148
|
|
@@ -235,6 +235,8 @@ TriggerResult = Algebrick.type do
|
|
235
235
|
PlaningFailed = type { fields! execution_plan_id: String, error: Exception }
|
236
236
|
# Returned by #trigger when planning is successful but execution fails to start.
|
237
237
|
ExecutionFailed = type { fields! execution_plan_id: String, error: Exception }
|
238
|
+
# Returned by #schedule when scheduling succeeded.
|
239
|
+
Scheduled = type { fields! execution_plan_id: String }
|
238
240
|
# Returned by #trigger when planning is successful, #future will resolve after
|
239
241
|
# ExecutionPlan is executed.
|
240
242
|
Triggered = type { fields! execution_plan_id: String, future: Future }
|
@@ -268,6 +270,37 @@ def self.trigger_task(async, action, *args, &block)
|
|
268
270
|
end
|
269
271
|
```
|
270
272
|
|
273
|
+
#### Scheduling
|
274
|
+
|
275
|
+
Scheduling an action means setting it up to be triggered at set time in future.
|
276
|
+
Any action can be scheduled by calling:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
world_instance.schedule(AnAction,
|
280
|
+
{ start_at: Time.now + 360, start_before: Time.now + 400 },
|
281
|
+
*args)
|
282
|
+
```
|
283
|
+
|
284
|
+
This snippet of code would schedule `AnAction` with arguments `args` to be executed
|
285
|
+
in the time interval between `start_at` and `start_before`. Setting `start_before` to `nil`
|
286
|
+
would schedule this action without the timeout limit.
|
287
|
+
|
288
|
+
When an action is scheduled, an execution plan object is created with state set
|
289
|
+
to `scheduled`, but it doesn't run the the plan phase yet, the planning happens
|
290
|
+
when the `start_at` time comes. If the planning doesn't happen in time
|
291
|
+
(e.g. after `start_before`), the execution plan is marked as failed
|
292
|
+
(its state is set to `stopped` and result to `error`).
|
293
|
+
|
294
|
+
Since the `args` have to be saved, there must be a mechanism to safely serialize and deserialize them
|
295
|
+
in order to make them survive being saved in a database. This is handled by a serializer.
|
296
|
+
Different serializers can be set per action by overriding its `schedule` method.
|
297
|
+
|
298
|
+
Planning of the scheduled plans is handled by `Scheduler`, an object which
|
299
|
+
periodically checks for scheduled execution plans and plans them. Scheduled execution
|
300
|
+
plans don't do anything by themselves, they just wait to be picked up and planned by a Scheduler.
|
301
|
+
It means that if no scheduler is present, their planning will be delayed until a scheduler
|
302
|
+
is spawned.
|
303
|
+
|
271
304
|
#### Plan phase
|
272
305
|
|
273
306
|
Planning always uses the thread triggering the action. Plan phase
|
@@ -319,7 +352,7 @@ end
|
|
319
352
|
|
320
353
|
{% info_block %}
|
321
354
|
|
322
|
-
It's considered a good practice to use
|
355
|
+
It's considered a good practice to use just enough data for the
|
323
356
|
input for the action to perform the job. That means not too much
|
324
357
|
(such as using ActiveRecord's attributes), as it might have
|
325
358
|
performance impact as well as causes issues when changing the
|
@@ -488,8 +521,8 @@ def plan
|
|
488
521
|
# so it's added to the above sequence to be executed as 4th.
|
489
522
|
action1 = plan_action AnAction, actions_executed_sequentially.last.output
|
490
523
|
|
491
|
-
# It's planned in default plan's concurrency scope it's executed concurrently
|
492
|
-
#
|
524
|
+
# It's planned in default plan's concurrency scope so it's executed concurrently
|
525
|
+
# with the other four actions.
|
493
526
|
action2 = plan_action AnAction
|
494
527
|
end
|
495
528
|
```
|
@@ -557,7 +590,7 @@ The usual execution looks as follows, we use an ActiveRecord User as example of
|
|
557
590
|
used. Potentially, saving some data that were retrieved in the `run`
|
558
591
|
phase back to the local database.
|
559
592
|
|
560
|
-
For that reason there are transactions around whole `plan` and `
|
593
|
+
For that reason there are transactions around whole `plan` and `finalize` phase
|
561
594
|
(all action's plan methods are in one transaction).
|
562
595
|
If anything goes wrong in the `plan` phase any change made during planning to local DB is
|
563
596
|
reverted. Same holds for finalizing, if anything goes wrong, all changes are reverted. Therefore
|
@@ -833,6 +866,7 @@ Each **Action phase** can be in one of the following states:
|
|
833
866
|
**Execution plan** has following states:
|
834
867
|
|
835
868
|
- **Pending** - Planning did not start yet.
|
869
|
+
- **Scheduled** - Scheduled for later execution, not yet planned.
|
836
870
|
- **Planning** - It's being planned.
|
837
871
|
- **Planned** - It've been planned, running phase did not start yet.
|
838
872
|
- **Running** - It's running, `run` and `finalize` phases of actions are executed.
|
@@ -851,12 +885,12 @@ Each **Action phase** can be in one of the following states:
|
|
851
885
|
|
852
886
|
### Error handling
|
853
887
|
|
854
|
-
If
|
855
|
-
for later inspection and it bubbles up in `World#trigger` method which was used to trigger
|
856
|
-
the action leading to this error.
|
888
|
+
If an error is raised in **`plan` phase**, it is persisted in the Action object
|
889
|
+
for later inspection and it bubbles up in `World#trigger` method which was used to trigger
|
890
|
+
the action leading to this error.
|
857
891
|
If you compare it to errors raised during `run` and `finalize` phase,
|
858
|
-
there's
|
859
|
-
in executor not in triggering Thread, they are just
|
892
|
+
there's one major difference: Those never bubble up in `trigger` because they are running
|
893
|
+
in executor not in triggering Thread, they are persisted just in the Action object.
|
860
894
|
|
861
895
|
If there is an error in **`run` phase**, the execution pauses. You can inspect the error in
|
862
896
|
[console](#console). The error may be intermittent or you may fix the problem manually. After
|
@@ -981,6 +1015,7 @@ client worlds: useful in production, see [develpment vs. production](#developmen
|
|
981
1015
|
client requests and other worlds
|
982
1016
|
1. **executor dispatcher** - responsible for getting requests from
|
983
1017
|
other worlds and sending the responses
|
1018
|
+
1. **scheduler** - responsible for planning and exectuion of scheduled tasks
|
984
1019
|
|
985
1020
|
{% plantuml %}
|
986
1021
|
|
@@ -1234,7 +1269,7 @@ The persistence making sure that the serialized states of the
|
|
1234
1269
|
execution plans are persisted for recovery and status tracking. The
|
1235
1270
|
execution plan data are stored in it, with the actual state.
|
1236
1271
|
|
1237
|
-
Unlike coordinator,
|
1272
|
+
Unlike coordinator, all the persisted data don't have to be
|
1238
1273
|
available for all the worlds at the same time: every world needs just
|
1239
1274
|
the data that it is actively working on. Also, all the data don't have to
|
1240
1275
|
be fully synchronized between worlds (as long as the up-to-date data
|
data/dynflow.gemspec
CHANGED
@@ -22,8 +22,8 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.add_dependency "multi_json"
|
23
23
|
s.add_dependency "apipie-params"
|
24
24
|
s.add_dependency "algebrick", '~> 0.7.0'
|
25
|
-
s.add_dependency "concurrent-ruby", '~> 0.9.0
|
26
|
-
s.add_dependency "concurrent-ruby-edge", '~> 0.1.0
|
25
|
+
s.add_dependency "concurrent-ruby", '~> 0.9.0'
|
26
|
+
s.add_dependency "concurrent-ruby-edge", '~> 0.1.0'
|
27
27
|
|
28
28
|
s.add_development_dependency "rack-test"
|
29
29
|
s.add_development_dependency "minitest"
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'example_helper'
|
4
|
+
|
5
|
+
class CustomPassedObject
|
6
|
+
attr_reader :id, :name
|
7
|
+
|
8
|
+
def initialize(id, name)
|
9
|
+
@id = id
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class CustomPassedObjectSerializer < ::Dynflow::Serializers::Abstract
|
15
|
+
def serialize(*args)
|
16
|
+
object = args.first
|
17
|
+
# Serialized output can be anything that is representable as JSON: Array, Hash...
|
18
|
+
{ :id => object.id, :name => object.name }
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize(serialized_args)
|
22
|
+
# Deserialized output must be an Array
|
23
|
+
[CustomPassedObject.new(serialized_args[:id], serialized_args[:name])]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class DelayedAction < Dynflow::Action
|
28
|
+
|
29
|
+
def schedule(schedule_options, *args)
|
30
|
+
CustomPassedObjectSerializer.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def plan(passed_object)
|
34
|
+
plan_self :object_id => passed_object.id, :object_name => passed_object.name
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
if $0 == __FILE__
|
43
|
+
ExampleHelper.world.action_logger.level = 1
|
44
|
+
ExampleHelper.world.logger.level = 0
|
45
|
+
|
46
|
+
past = Time.now - 200
|
47
|
+
near_future = Time.now + 29
|
48
|
+
future = Time.now + 180
|
49
|
+
|
50
|
+
object = CustomPassedObject.new(1, 'CPS')
|
51
|
+
|
52
|
+
past_plan = ExampleHelper.world.schedule(DelayedAction, { :start_at => past, :start_before => past }, object)
|
53
|
+
near_future_plan = ExampleHelper.world.schedule(DelayedAction, { :start_at => near_future, :start_before => future }, object)
|
54
|
+
future_plan = ExampleHelper.world.schedule(DelayedAction, { :start_at => future }, object)
|
55
|
+
|
56
|
+
puts <<-MSG.gsub(/^.*\|/, '')
|
57
|
+
|
|
58
|
+
| Future Execution Example
|
59
|
+
| ========================
|
60
|
+
|
|
61
|
+
| This example shows the future execution functionality of Dynflow, which allows to plan actions to be executed at set time.
|
62
|
+
|
|
63
|
+
| Execution plans:
|
64
|
+
| #{past_plan.id} is scheduled to execute before #{past} and should timeout on the first run of the scheduler.
|
65
|
+
| #{near_future_plan.id} is scheduled to execute at #{near_future} and should run successfully.
|
66
|
+
| #{future_plan.id} is scheduled to execute at #{future} and should run successfully.
|
67
|
+
|
|
68
|
+
| Visit http://localhost:4567 to see their status.
|
69
|
+
|
|
70
|
+
MSG
|
71
|
+
|
72
|
+
ExampleHelper.run_web_console
|
73
|
+
end
|
data/lib/dynflow.rb
CHANGED
@@ -9,7 +9,7 @@ require 'concurrent-edge'
|
|
9
9
|
|
10
10
|
logger = Logger.new($stderr)
|
11
11
|
logger.level = Logger::INFO
|
12
|
-
Concurrent.
|
12
|
+
Concurrent.global_logger = lambda do |level, progname, message = nil, &block|
|
13
13
|
logger.add level, message, progname, &block
|
14
14
|
end
|
15
15
|
|
@@ -36,11 +36,14 @@ module Dynflow
|
|
36
36
|
require 'dynflow/flows'
|
37
37
|
require 'dynflow/execution_history'
|
38
38
|
require 'dynflow/execution_plan'
|
39
|
+
require 'dynflow/scheduled_plan'
|
39
40
|
require 'dynflow/action'
|
40
41
|
require 'dynflow/executors'
|
41
42
|
require 'dynflow/logger_adapters'
|
42
43
|
require 'dynflow/world'
|
43
44
|
require 'dynflow/connectors'
|
44
45
|
require 'dynflow/dispatcher'
|
46
|
+
require 'dynflow/serializers'
|
47
|
+
require 'dynflow/schedulers'
|
45
48
|
require 'dynflow/config'
|
46
49
|
end
|
data/lib/dynflow/action.rb
CHANGED
@@ -282,6 +282,17 @@ module Dynflow
|
|
282
282
|
recursion.(input)
|
283
283
|
end
|
284
284
|
|
285
|
+
def execute_schedule(schedule_options, *args)
|
286
|
+
world.middleware.execute(:schedule, self, schedule_options, *args) do
|
287
|
+
@serializer = schedule(schedule_options, *args)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def serializer
|
292
|
+
raise "The action must be scheduled in order to access the serializer" if @serializer.nil?
|
293
|
+
@serializer
|
294
|
+
end
|
295
|
+
|
285
296
|
protected
|
286
297
|
|
287
298
|
def state=(state)
|
@@ -298,6 +309,10 @@ module Dynflow
|
|
298
309
|
@step.save
|
299
310
|
end
|
300
311
|
|
312
|
+
def schedule(schedule_options, *args)
|
313
|
+
Serializers::Noop.new
|
314
|
+
end
|
315
|
+
|
301
316
|
# @override to implement the action's *Plan phase* behaviour.
|
302
317
|
# By default it plans itself and expects input-hash.
|
303
318
|
# Use #plan_self and #plan_action methods to plan actions.
|
data/lib/dynflow/config.rb
CHANGED
@@ -70,7 +70,15 @@ module Dynflow
|
|
70
70
|
end
|
71
71
|
|
72
72
|
config_attr :auto_rescue, Algebrick::Types::Boolean do
|
73
|
-
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
config_attr :auto_validity_check, Algebrick::Types::Boolean do |world, config|
|
77
|
+
!!config.executor
|
78
|
+
end
|
79
|
+
|
80
|
+
config_attr :validity_check_timeout, Fixnum do
|
81
|
+
5
|
74
82
|
end
|
75
83
|
|
76
84
|
config_attr :exit_on_terminate, Algebrick::Types::Boolean do
|
@@ -85,6 +93,12 @@ module Dynflow
|
|
85
93
|
true
|
86
94
|
end
|
87
95
|
|
96
|
+
config_attr :scheduler, Schedulers::Abstract, NilClass do |world|
|
97
|
+
options = { :poll_interval => 15,
|
98
|
+
:time_source => -> { Time.now.utc } }
|
99
|
+
Schedulers::Polling.new(world, options)
|
100
|
+
end
|
101
|
+
|
88
102
|
config_attr :action_classes do
|
89
103
|
Action.all_children
|
90
104
|
end
|
data/lib/dynflow/coordinator.rb
CHANGED
@@ -157,6 +157,13 @@ module Dynflow
|
|
157
157
|
|
158
158
|
end
|
159
159
|
|
160
|
+
class SchedulerLock < LockByWorld
|
161
|
+
def initialize(world)
|
162
|
+
super
|
163
|
+
@data[:id] = "scheduler"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
160
167
|
class WorldInvalidationLock < LockByWorld
|
161
168
|
def initialize(world, invalidated_world)
|
162
169
|
super(world)
|
@@ -16,11 +16,12 @@ module Dynflow
|
|
16
16
|
:started_at, :ended_at, :execution_time, :real_time, :execution_history
|
17
17
|
|
18
18
|
def self.states
|
19
|
-
@states ||= [:pending, :planning, :planned, :running, :paused, :stopped]
|
19
|
+
@states ||= [:pending, :scheduled, :planning, :planned, :running, :paused, :stopped]
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.state_transitions
|
23
|
-
@state_transitions ||= { pending: [:planning],
|
23
|
+
@state_transitions ||= { pending: [:scheduled, :planning],
|
24
|
+
scheduled: [:planning, :stopped],
|
24
25
|
planning: [:planned, :stopped],
|
25
26
|
planned: [:running],
|
26
27
|
running: [:paused, :stopped],
|
@@ -72,7 +73,7 @@ module Dynflow
|
|
72
73
|
@started_at = Time.now
|
73
74
|
when :stopped
|
74
75
|
@ended_at = Time.now
|
75
|
-
@real_time = @ended_at - @started_at
|
76
|
+
@real_time = @ended_at - @started_at unless @started_at.nil?
|
76
77
|
@execution_time = compute_execution_time
|
77
78
|
else
|
78
79
|
# ignore
|
@@ -151,6 +152,17 @@ module Dynflow
|
|
151
152
|
@last_step_id += 1
|
152
153
|
end
|
153
154
|
|
155
|
+
def schedule(action_class, options, schedule_options, *args)
|
156
|
+
prepare(action_class, options)
|
157
|
+
execution_history.add("schedule", @world.id)
|
158
|
+
update_state :scheduled
|
159
|
+
entry_action.execute_schedule(schedule_options, args)
|
160
|
+
end
|
161
|
+
|
162
|
+
def schedule_record
|
163
|
+
@schedule_record ||= persistence.load_scheduled_plan(id)
|
164
|
+
end
|
165
|
+
|
154
166
|
def prepare(action_class, options = {})
|
155
167
|
options = options.dup
|
156
168
|
caller_action = Type! options.delete(:caller_action), Dynflow::Action, NilClass
|
@@ -51,7 +51,7 @@ module Dynflow
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def self.state_transitions
|
54
|
-
@state_transitions ||= { pending: [:running],
|
54
|
+
@state_transitions ||= { pending: [:running, :error],
|
55
55
|
running: [:success, :error],
|
56
56
|
success: [],
|
57
57
|
suspended: [],
|
@@ -76,6 +76,10 @@ module Dynflow
|
|
76
76
|
hash[:children]
|
77
77
|
end
|
78
78
|
|
79
|
+
def load_action
|
80
|
+
@action = @world.persistence.load_action(self)
|
81
|
+
end
|
82
|
+
|
79
83
|
def initialize_action(caller_action)
|
80
84
|
attributes = { execution_plan_id: execution_plan_id,
|
81
85
|
id: action_id,
|
data/lib/dynflow/middleware.rb
CHANGED
@@ -14,7 +14,7 @@ module Dynflow
|
|
14
14
|
@middleware_class = Child! middleware_class, Middleware
|
15
15
|
@middleware = middleware_class.new self
|
16
16
|
@action = Type! action, Dynflow::Action, NilClass
|
17
|
-
@method = Match! method, :plan, :run, :finalize, :plan_phase, :finalize_phase
|
17
|
+
@method = Match! method, :schedule, :plan, :run, :finalize, :plan_phase, :finalize_phase
|
18
18
|
@next_stack = Type! next_stack, Middleware::Stack, Proc
|
19
19
|
end
|
20
20
|
|
@@ -14,7 +14,7 @@ module Dynflow
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def execute(method, action_or_class, *args, &block)
|
17
|
-
Match! method, :plan, :run, :finalize, :plan_phase, :finalize_phase
|
17
|
+
Match! method, :schedule, :plan, :run, :finalize, :plan_phase, :finalize_phase
|
18
18
|
if Child? action_or_class, Dynflow::Action
|
19
19
|
action = nil
|
20
20
|
action_class = action_or_class
|
data/lib/dynflow/persistence.rb
CHANGED
@@ -49,6 +49,25 @@ module Dynflow
|
|
49
49
|
adapter.save_execution_plan(execution_plan.id, execution_plan.to_hash)
|
50
50
|
end
|
51
51
|
|
52
|
+
def find_past_scheduled_plans(time)
|
53
|
+
adapter.find_past_scheduled_plans(time).map do |plan|
|
54
|
+
ScheduledPlan.new_from_hash(@world, plan)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_scheduled_plans(filters, batch_size = 1000)
|
59
|
+
adapter.delete_scheduled_plans(filters, batch_size)
|
60
|
+
end
|
61
|
+
|
62
|
+
def save_scheduled_plan(schedule)
|
63
|
+
adapter.save_scheduled_plan(schedule.execution_plan_uuid, schedule.to_hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_scheduled_plan(execution_plan_id)
|
67
|
+
hash = adapter.load_scheduled_plan(execution_plan_id)
|
68
|
+
ScheduledPlan.new_from_hash(@world, hash)
|
69
|
+
end
|
70
|
+
|
52
71
|
def load_step(execution_plan_id, step_id, world)
|
53
72
|
step_hash = adapter.load_step(execution_plan_id, step_id)
|
54
73
|
ExecutionPlan::Steps::Abstract.from_hash(step_hash, execution_plan_id, world)
|