dynflow 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|