dynflow 0.8.37 → 1.0.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.
- checksums.yaml +4 -4
- data/doc/pages/source/documentation/index.md +39 -0
- data/examples/orchestrate.rb +8 -0
- data/lib/dynflow/action.rb +7 -0
- data/lib/dynflow/active_job/queue_adapter.rb +5 -0
- data/lib/dynflow/config.rb +49 -12
- data/lib/dynflow/coordinator.rb +11 -0
- data/lib/dynflow/director.rb +9 -8
- data/lib/dynflow/director/execution_plan_manager.rb +2 -2
- data/lib/dynflow/director/running_steps_manager.rb +1 -1
- data/lib/dynflow/execution_plan.rb +2 -2
- data/lib/dynflow/execution_plan/steps/abstract.rb +12 -5
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +8 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +5 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +5 -0
- data/lib/dynflow/executors/parallel.rb +2 -2
- data/lib/dynflow/executors/parallel/core.rb +42 -14
- data/lib/dynflow/executors/parallel/pool.rb +3 -2
- data/lib/dynflow/persistence_adapters/sequel.rb +1 -1
- data/lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb +7 -0
- data/lib/dynflow/rails.rb +1 -0
- data/lib/dynflow/rails/configuration.rb +31 -11
- data/lib/dynflow/utils.rb +10 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web/console.rb +9 -7
- data/lib/dynflow/web/console_helpers.rb +16 -0
- data/lib/dynflow/web/filtering_helpers.rb +5 -0
- data/lib/dynflow/world.rb +28 -28
- data/test/activejob_adapter_test.rb +6 -2
- data/test/support/dummy_example.rb +4 -0
- data/test/test_helper.rb +1 -0
- data/test/world_test.rb +9 -4
- data/web/views/world_validation_result.erb +15 -0
- data/web/views/worlds.erb +30 -23
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 111e155822b2ec8d00a8f0e249ce1333f6d4e826bb10247e172a523e7212a9d6
|
4
|
+
data.tar.gz: 4969844384cd6ea8add967e10495cc534bee36dbbe4fe5a9b978ecd8c09d802c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65b612808157a76872102e4070bbb0b0ca8e23d9eba4f032a7881d67b884f33be74dc0fc7fbbcd458d15be675e33f10ba00c0fb24e4c3e237a69e7ae27558104
|
7
|
+
data.tar.gz: 66c6e35276566b7d239a25c5f95e8b52331e70aa32f36a36027c3fbf64d64069f611b8ffaab3caac5a89bf370a4c6fadc3bb5736de8bde9d3b7f50bf7a962149
|
@@ -940,6 +940,45 @@ Solutions are:
|
|
940
940
|
- **Offloading computation** - CPU heavy parts can be offloaded to different services
|
941
941
|
notifying the suspended actions when the computation is done.
|
942
942
|
|
943
|
+
### Multiple queues support
|
944
|
+
|
945
|
+
By default, a single queue and a pool of workers is used to process
|
946
|
+
all the actions in the system. This can cause some actions to block
|
947
|
+
execution of some higher-priority ones in the system.
|
948
|
+
|
949
|
+
To address this case, it's possible to define additional queues tied
|
950
|
+
to additional pool of workers dedicated for it. This way, they can be
|
951
|
+
processed more independently from the default queue.
|
952
|
+
|
953
|
+
To use the queue, one needs to register additional queues when defining
|
954
|
+
the executor world:
|
955
|
+
|
956
|
+
```ruby
|
957
|
+
config = Dynflow::Config.new
|
958
|
+
config.queues.add(:slow, :pool_size => 5)
|
959
|
+
world = Dynflow::World.new(config)
|
960
|
+
```
|
961
|
+
|
962
|
+
The action to use the queue just needs to override the `queue` method like this:
|
963
|
+
|
964
|
+
|
965
|
+
```ruby
|
966
|
+
class MyAction < Dynflow::Action
|
967
|
+
def queue
|
968
|
+
:slow
|
969
|
+
end
|
970
|
+
|
971
|
+
def run
|
972
|
+
sleep 60
|
973
|
+
end
|
974
|
+
end
|
975
|
+
```
|
976
|
+
|
977
|
+
In the current implementation, it's expected all the executors would
|
978
|
+
have the same set of queues defined. In the future implementation it
|
979
|
+
should be possible to have dedicated executors with just a subset of
|
980
|
+
queues
|
981
|
+
|
943
982
|
### Middleware
|
944
983
|
|
945
984
|
Each action class has chain of middlewares which wrap phases of the action execution.
|
data/examples/orchestrate.rb
CHANGED
@@ -71,6 +71,10 @@ module Orchestrate
|
|
71
71
|
|
72
72
|
class PrepareDisk < Base
|
73
73
|
|
74
|
+
def queue
|
75
|
+
:slow
|
76
|
+
end
|
77
|
+
|
74
78
|
input_format do
|
75
79
|
param :name
|
76
80
|
end
|
@@ -144,6 +148,10 @@ module Orchestrate
|
|
144
148
|
end
|
145
149
|
|
146
150
|
if $0 == __FILE__
|
151
|
+
world = ExampleHelper.create_world do |config|
|
152
|
+
config.queues.add(:slow, :pool_size => 3)
|
153
|
+
end
|
154
|
+
ExampleHelper.set_world(world)
|
147
155
|
ExampleHelper.world.action_logger.level = Logger::INFO
|
148
156
|
ExampleHelper.something_should_fail!
|
149
157
|
ExampleHelper.world.trigger(Orchestrate::CreateInfrastructure)
|
data/lib/dynflow/action.rb
CHANGED
@@ -180,6 +180,7 @@ module Dynflow
|
|
180
180
|
@from_subscription = Type! from_subscription, TrueClass, FalseClass
|
181
181
|
end
|
182
182
|
|
183
|
+
# action that caused this action to be planned. Available only in planning phase
|
183
184
|
def triggering_action
|
184
185
|
phase! Plan
|
185
186
|
@triggering_action
|
@@ -316,6 +317,12 @@ module Dynflow
|
|
316
317
|
false
|
317
318
|
end
|
318
319
|
|
320
|
+
# @override define what pool should the action be run in. The
|
321
|
+
# queue defined here will also be used as the default queue for
|
322
|
+
# all the steps planned under this action, unless overrided by sub-action
|
323
|
+
def queue
|
324
|
+
end
|
325
|
+
|
319
326
|
protected
|
320
327
|
|
321
328
|
def state=(state)
|
@@ -23,9 +23,14 @@ module Dynflow
|
|
23
23
|
end
|
24
24
|
|
25
25
|
class JobWrapper < Dynflow::Action
|
26
|
+
def queue
|
27
|
+
input[:queue].to_sym
|
28
|
+
end
|
29
|
+
|
26
30
|
def plan(attributes)
|
27
31
|
input[:job_class] = attributes['job_class']
|
28
32
|
input[:job_arguments] = attributes['arguments']
|
33
|
+
input[:queue] = attributes['queue_name']
|
29
34
|
plan_self
|
30
35
|
end
|
31
36
|
|
data/lib/dynflow/config.rb
CHANGED
@@ -32,15 +32,51 @@ module Dynflow
|
|
32
32
|
@config.validate(self)
|
33
33
|
end
|
34
34
|
|
35
|
+
def queues
|
36
|
+
@queues ||= @config.queues.finalized_config(self)
|
37
|
+
end
|
38
|
+
|
35
39
|
def method_missing(name)
|
36
40
|
return @cache[name] if @cache.key?(name)
|
37
41
|
value = @config.send(name)
|
38
42
|
value = value.call(@world, self) if value.is_a? Proc
|
39
|
-
|
43
|
+
validation_method = "validate_#{ name }!"
|
44
|
+
@config.send(validation_method, value) if @config.respond_to?(validation_method)
|
40
45
|
@cache[name] = value
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
49
|
+
class QueuesConfig
|
50
|
+
attr_reader :queues
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@queues = {:default => {}}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Add a new queue to the configuration
|
57
|
+
#
|
58
|
+
# @param [Hash] queue_options
|
59
|
+
# @option queue_options :pool_size The amount of workers available for the queue.
|
60
|
+
# By default, it uses global pool_size config option.
|
61
|
+
def add(name, queue_options = {})
|
62
|
+
Utils.validate_keys!(queue_options, :pool_size)
|
63
|
+
name = name.to_sym
|
64
|
+
raise ArgumentError, "Queue #{name} is already defined" if @queues.key?(name)
|
65
|
+
@queues[name] = queue_options
|
66
|
+
end
|
67
|
+
|
68
|
+
def finalized_config(config_for_world)
|
69
|
+
@queues.values.each do |queue_options|
|
70
|
+
queue_options[:pool_size] ||= config_for_world.pool_size
|
71
|
+
end
|
72
|
+
@queues
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def queues
|
77
|
+
@queues ||= QueuesConfig.new
|
78
|
+
end
|
79
|
+
|
44
80
|
config_attr :logger_adapter, LoggerAdapters::Abstract do
|
45
81
|
LoggerAdapters::Simple.new
|
46
82
|
end
|
@@ -62,7 +98,7 @@ module Dynflow
|
|
62
98
|
end
|
63
99
|
|
64
100
|
config_attr :executor, Executors::Abstract, FalseClass do |world, config|
|
65
|
-
Executors::Parallel.new(world, config.
|
101
|
+
Executors::Parallel.new(world, config.queues)
|
66
102
|
end
|
67
103
|
|
68
104
|
config_attr :executor_semaphore, Semaphores::Abstract, FalseClass do |world, config|
|
@@ -126,7 +162,7 @@ module Dynflow
|
|
126
162
|
Action.all_children
|
127
163
|
end
|
128
164
|
|
129
|
-
config_attr :meta do
|
165
|
+
config_attr :meta do |world, config|
|
130
166
|
{ 'hostname' => Socket.gethostname, 'pid' => Process.pid }
|
131
167
|
end
|
132
168
|
|
@@ -140,17 +176,18 @@ module Dynflow
|
|
140
176
|
|
141
177
|
def validate(config_for_world)
|
142
178
|
if defined? ::ActiveRecord::Base
|
143
|
-
|
144
|
-
|
145
|
-
config_for_world.
|
146
|
-
|
147
|
-
|
179
|
+
begin
|
180
|
+
ar_pool_size = ::ActiveRecord::Base.connection_pool.instance_variable_get(:@size)
|
181
|
+
if (config_for_world.pool_size / 2.0) > ar_pool_size
|
182
|
+
config_for_world.world.logger.warn 'Consider increasing ActiveRecord::Base.connection_pool size, ' +
|
183
|
+
"it's #{ar_pool_size} but there is #{config_for_world.pool_size} " +
|
184
|
+
'threads in Dynflow pool.'
|
185
|
+
end
|
186
|
+
rescue ActiveRecord::ConnectionNotEstablished # rubocop:disable Lint/HandleExceptions
|
187
|
+
# If in tests or in an environment where ActiveRecord doesn't have a
|
188
|
+
# real DB connection, we want to skip AR configuration altogether
|
148
189
|
end
|
149
190
|
end
|
150
|
-
|
151
|
-
rescue ActiveRecord::ConnectionNotEstablished # rubocop:disable Lint/HandleExceptions
|
152
|
-
# If in tests or in an environment where ActiveRecord doesn't have a
|
153
|
-
# real DB connection, we want to skip AR configuration altogether
|
154
191
|
end
|
155
192
|
end
|
156
193
|
end
|
data/lib/dynflow/coordinator.rb
CHANGED
@@ -89,6 +89,10 @@ module Dynflow
|
|
89
89
|
def meta
|
90
90
|
@data[:meta]
|
91
91
|
end
|
92
|
+
|
93
|
+
def executor?
|
94
|
+
raise NotImplementedError
|
95
|
+
end
|
92
96
|
end
|
93
97
|
|
94
98
|
class ExecutorWorld < WorldRecord
|
@@ -105,9 +109,16 @@ module Dynflow
|
|
105
109
|
Type! value, Algebrick::Types::Boolean
|
106
110
|
@data[:active] = value
|
107
111
|
end
|
112
|
+
|
113
|
+
def executor?
|
114
|
+
true
|
115
|
+
end
|
108
116
|
end
|
109
117
|
|
110
118
|
class ClientWorld < WorldRecord
|
119
|
+
def executor?
|
120
|
+
false
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
class Lock < Record
|
data/lib/dynflow/director.rb
CHANGED
@@ -19,10 +19,11 @@ module Dynflow
|
|
19
19
|
UnprocessableEvent = Class.new(Dynflow::Error)
|
20
20
|
|
21
21
|
class WorkItem
|
22
|
-
attr_reader :execution_plan_id
|
22
|
+
attr_reader :execution_plan_id, :queue
|
23
23
|
|
24
|
-
def initialize(execution_plan_id)
|
24
|
+
def initialize(execution_plan_id, queue)
|
25
25
|
@execution_plan_id = execution_plan_id
|
26
|
+
@queue = queue
|
26
27
|
end
|
27
28
|
|
28
29
|
def execute
|
@@ -33,8 +34,8 @@ module Dynflow
|
|
33
34
|
class StepWorkItem < WorkItem
|
34
35
|
attr_reader :step
|
35
36
|
|
36
|
-
def initialize(execution_plan_id, step)
|
37
|
-
super(execution_plan_id)
|
37
|
+
def initialize(execution_plan_id, step, queue)
|
38
|
+
super(execution_plan_id, queue)
|
38
39
|
@step = step
|
39
40
|
end
|
40
41
|
|
@@ -46,8 +47,8 @@ module Dynflow
|
|
46
47
|
class EventWorkItem < StepWorkItem
|
47
48
|
attr_reader :event
|
48
49
|
|
49
|
-
def initialize(execution_plan_id, step, event)
|
50
|
-
super(execution_plan_id, step)
|
50
|
+
def initialize(execution_plan_id, step, event, queue)
|
51
|
+
super(execution_plan_id, step, queue)
|
51
52
|
@event = event
|
52
53
|
end
|
53
54
|
|
@@ -57,8 +58,8 @@ module Dynflow
|
|
57
58
|
end
|
58
59
|
|
59
60
|
class FinalizeWorkItem < WorkItem
|
60
|
-
def initialize(execution_plan_id, sequential_manager)
|
61
|
-
super(execution_plan_id)
|
61
|
+
def initialize(execution_plan_id, sequential_manager, queue)
|
62
|
+
super(execution_plan_id, queue)
|
62
63
|
@sequential_manager = sequential_manager
|
63
64
|
end
|
64
65
|
|
@@ -25,7 +25,7 @@ module Dynflow
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def prepare_next_step(step)
|
28
|
-
StepWorkItem.new(execution_plan.id, step).tap do |work|
|
28
|
+
StepWorkItem.new(execution_plan.id, step, step.queue).tap do |work|
|
29
29
|
@running_steps_manager.add(step, work)
|
30
30
|
end
|
31
31
|
end
|
@@ -93,7 +93,7 @@ module Dynflow
|
|
93
93
|
return if execution_plan.finalize_flow.empty?
|
94
94
|
raise 'finalize phase already started' if @finalize_manager
|
95
95
|
@finalize_manager = SequentialManager.new(@world, execution_plan)
|
96
|
-
[FinalizeWorkItem.new(execution_plan.id, @finalize_manager)]
|
96
|
+
[FinalizeWorkItem.new(execution_plan.id, @finalize_manager, execution_plan.finalize_steps.first.queue)]
|
97
97
|
end
|
98
98
|
|
99
99
|
def finish
|
@@ -69,7 +69,7 @@ module Dynflow
|
|
69
69
|
end
|
70
70
|
|
71
71
|
can_run_event = @events.empty?(step.id)
|
72
|
-
work = EventWorkItem.new(event.execution_plan_id, step, event)
|
72
|
+
work = EventWorkItem.new(event.execution_plan_id, step, event, step.queue)
|
73
73
|
@events.push(step.id, work)
|
74
74
|
next_work_items << work if can_run_event
|
75
75
|
next_work_items
|
@@ -389,7 +389,7 @@ module Dynflow
|
|
389
389
|
|
390
390
|
def add_run_step(action)
|
391
391
|
add_step(Steps::RunStep, action.class, action.id).tap do |step|
|
392
|
-
step.
|
392
|
+
step.update_from_action(action)
|
393
393
|
@dependency_graph.add_dependencies(step, action)
|
394
394
|
current_run_flow.add_and_resolve(@dependency_graph, Flows::Atom.new(step.id))
|
395
395
|
end
|
@@ -397,7 +397,7 @@ module Dynflow
|
|
397
397
|
|
398
398
|
def add_finalize_step(action)
|
399
399
|
add_step(Steps::FinalizeStep, action.class, action.id).tap do |step|
|
400
|
-
step.
|
400
|
+
step.update_from_action(action)
|
401
401
|
finalize_flow << Flows::Atom.new(step.id)
|
402
402
|
end
|
403
403
|
end
|
@@ -5,9 +5,10 @@ module Dynflow
|
|
5
5
|
include Stateful
|
6
6
|
|
7
7
|
attr_reader :execution_plan_id, :id, :state, :action_class, :action_id, :world, :started_at,
|
8
|
-
:ended_at, :execution_time, :real_time
|
8
|
+
:ended_at, :execution_time, :real_time, :queue
|
9
9
|
attr_accessor :error
|
10
10
|
|
11
|
+
# rubocop:disable Metrics/ParameterLists
|
11
12
|
def initialize(execution_plan_id,
|
12
13
|
id,
|
13
14
|
state,
|
@@ -20,7 +21,8 @@ module Dynflow
|
|
20
21
|
execution_time = 0.0,
|
21
22
|
real_time = 0.0,
|
22
23
|
progress_done = nil,
|
23
|
-
progress_weight = nil
|
24
|
+
progress_weight = nil,
|
25
|
+
queue = nil)
|
24
26
|
|
25
27
|
@id = id || raise(ArgumentError, 'missing id')
|
26
28
|
@execution_plan_id = Type! execution_plan_id, String
|
@@ -34,6 +36,8 @@ module Dynflow
|
|
34
36
|
@progress_done = Type! progress_done, Numeric, NilClass
|
35
37
|
@progress_weight = Type! progress_weight, Numeric, NilClass
|
36
38
|
|
39
|
+
@queue = Type! queue, Symbol, NilClass
|
40
|
+
|
37
41
|
self.state = state.to_sym
|
38
42
|
|
39
43
|
Child! action_class, Action
|
@@ -41,6 +45,7 @@ module Dynflow
|
|
41
45
|
|
42
46
|
@action_id = action_id || raise(ArgumentError, 'missing action_id')
|
43
47
|
end
|
48
|
+
# rubocop:enable Metrics/ParameterLists
|
44
49
|
|
45
50
|
def action_logger
|
46
51
|
@world.action_logger
|
@@ -87,7 +92,8 @@ module Dynflow
|
|
87
92
|
execution_time: execution_time,
|
88
93
|
real_time: real_time,
|
89
94
|
progress_done: progress_done,
|
90
|
-
progress_weight: progress_weight
|
95
|
+
progress_weight: progress_weight,
|
96
|
+
queue: queue
|
91
97
|
end
|
92
98
|
|
93
99
|
def progress_done
|
@@ -132,7 +138,7 @@ module Dynflow
|
|
132
138
|
|
133
139
|
def self.new_from_hash(hash, execution_plan_id, world)
|
134
140
|
check_class_matching hash
|
135
|
-
new
|
141
|
+
new(execution_plan_id,
|
136
142
|
hash[:id],
|
137
143
|
hash[:state],
|
138
144
|
Action.constantize(hash[:action_class]),
|
@@ -144,7 +150,8 @@ module Dynflow
|
|
144
150
|
hash[:execution_time].to_f,
|
145
151
|
hash[:real_time].to_f,
|
146
152
|
hash[:progress_done].to_f,
|
147
|
-
hash[:progress_weight].to_f
|
153
|
+
hash[:progress_weight].to_f,
|
154
|
+
(hash[:queue] && hash[:queue].to_sym))
|
148
155
|
end
|
149
156
|
|
150
157
|
private
|
@@ -2,6 +2,14 @@ module Dynflow
|
|
2
2
|
module ExecutionPlan::Steps
|
3
3
|
class AbstractFlowStep < Abstract
|
4
4
|
|
5
|
+
# Method called when initializing the step to customize the behavior based on the
|
6
|
+
# action definition during the planning phase
|
7
|
+
def update_from_action(action)
|
8
|
+
@queue = action.queue
|
9
|
+
@queue ||= action.triggering_action.queue if action.triggering_action
|
10
|
+
@queue ||= :default
|
11
|
+
end
|
12
|
+
|
5
13
|
def execute(*args)
|
6
14
|
return self if [:skipped, :success].include? self.state
|
7
15
|
open_action do |action|
|
@@ -5,10 +5,10 @@ module Dynflow
|
|
5
5
|
require 'dynflow/executors/parallel/pool'
|
6
6
|
require 'dynflow/executors/parallel/worker'
|
7
7
|
|
8
|
-
def initialize(world,
|
8
|
+
def initialize(world, queues_options = { :default => { :pool_size => 5 }})
|
9
9
|
super(world)
|
10
10
|
@core = Core.spawn name: 'parallel-executor-core',
|
11
|
-
args: [world,
|
11
|
+
args: [world, queues_options],
|
12
12
|
initialized: @core_initialized = Concurrent.future
|
13
13
|
end
|
14
14
|
|
@@ -1,16 +1,28 @@
|
|
1
1
|
module Dynflow
|
2
2
|
module Executors
|
3
3
|
class Parallel < Abstract
|
4
|
-
|
5
4
|
class Core < Actor
|
6
5
|
attr_reader :logger
|
7
6
|
|
8
|
-
def initialize(world,
|
9
|
-
@logger
|
10
|
-
@world
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
7
|
+
def initialize(world, queues_options)
|
8
|
+
@logger = world.logger
|
9
|
+
@world = Type! world, World
|
10
|
+
@queues_options = queues_options
|
11
|
+
@pools = {}
|
12
|
+
@terminated = nil
|
13
|
+
@director = Director.new(@world)
|
14
|
+
|
15
|
+
initialize_queues
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_queues
|
19
|
+
default_pool_size = @queues_options[:default][:pool_size]
|
20
|
+
@queues_options.each do |(queue_name, queue_options)|
|
21
|
+
queue_pool_size = queue_options.fetch(:pool_size, default_pool_size)
|
22
|
+
@pools[queue_name] = Pool.spawn("pool #{queue_name}", reference,
|
23
|
+
queue_name, queue_pool_size,
|
24
|
+
@world.transaction_adapter)
|
25
|
+
end
|
14
26
|
end
|
15
27
|
|
16
28
|
def handle_execution(execution_plan_id, finished)
|
@@ -43,14 +55,17 @@ module Dynflow
|
|
43
55
|
|
44
56
|
def start_termination(*args)
|
45
57
|
super
|
46
|
-
logger.info 'shutting down
|
47
|
-
@pool.tell([:start_termination, Concurrent.future])
|
58
|
+
logger.info 'shutting down Core ...'
|
59
|
+
@pools.values.each { |pool| pool.tell([:start_termination, Concurrent.future]) }
|
48
60
|
end
|
49
61
|
|
50
|
-
def finish_termination
|
62
|
+
def finish_termination(pool_name)
|
63
|
+
@pools.delete(pool_name)
|
64
|
+
# we expect this message from all worker pools
|
65
|
+
return unless @pools.empty?
|
51
66
|
@director.terminate
|
52
|
-
logger.error '...
|
53
|
-
super
|
67
|
+
logger.error '... core terminated.'
|
68
|
+
super()
|
54
69
|
end
|
55
70
|
|
56
71
|
def dead_letter_routing
|
@@ -58,7 +73,9 @@ module Dynflow
|
|
58
73
|
end
|
59
74
|
|
60
75
|
def execution_status(execution_plan_id = nil)
|
61
|
-
@
|
76
|
+
@pools.each_with_object({}) do |(pool_name, pool), hash|
|
77
|
+
hash[pool_name] = pool.ask!([:execution_status, execution_plan_id])
|
78
|
+
end
|
62
79
|
end
|
63
80
|
|
64
81
|
private
|
@@ -74,7 +91,18 @@ module Dynflow
|
|
74
91
|
return if work_items.nil?
|
75
92
|
work_items = [work_items] if work_items.is_a? Director::WorkItem
|
76
93
|
work_items.all? { |i| Type! i, Director::WorkItem }
|
77
|
-
work_items.each
|
94
|
+
work_items.each do |new_work|
|
95
|
+
pool = @pools[new_work.queue]
|
96
|
+
unless pool
|
97
|
+
logger.error("Pool is not available for queue #{new_work.queue}, falling back to #{fallback_queue}")
|
98
|
+
pool = @pools[fallback_queue]
|
99
|
+
end
|
100
|
+
pool.tell([:schedule_work, new_work])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def fallback_queue
|
105
|
+
:default
|
78
106
|
end
|
79
107
|
end
|
80
108
|
end
|
@@ -46,7 +46,8 @@ module Dynflow
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def initialize(core, pool_size, transaction_adapter)
|
49
|
+
def initialize(core, name, pool_size, transaction_adapter)
|
50
|
+
@name = name
|
50
51
|
@executor_core = core
|
51
52
|
@pool_size = pool_size
|
52
53
|
@free_workers = Array.new(pool_size) { |i| Worker.spawn("worker-#{i}", reference, transaction_adapter) }
|
@@ -84,7 +85,7 @@ module Dynflow
|
|
84
85
|
def try_to_terminate
|
85
86
|
if terminating? && @free_workers.size == @pool_size
|
86
87
|
@free_workers.map { |worker| worker.ask(:terminate!) }.map(&:wait)
|
87
|
-
@executor_core.tell(:finish_termination)
|
88
|
+
@executor_core.tell([:finish_termination, @name])
|
88
89
|
finish_termination
|
89
90
|
end
|
90
91
|
end
|
@@ -33,7 +33,7 @@ module Dynflow
|
|
33
33
|
META_DATA = { execution_plan: %w(label state result started_at ended_at real_time execution_time root_plan_step_id class),
|
34
34
|
action: %w(caller_execution_plan_id caller_action_id class plan_step_id run_step_id finalize_step_id),
|
35
35
|
step: %w(state started_at ended_at real_time execution_time action_id progress_done progress_weight
|
36
|
-
class action_class execution_plan_uuid),
|
36
|
+
class action_class execution_plan_uuid queue),
|
37
37
|
envelope: %w(receiver_id),
|
38
38
|
coordinator_record: %w(id owner_id class),
|
39
39
|
delayed: %w(execution_plan_uuid start_at start_before args_serializer)}
|
data/lib/dynflow/rails.rb
CHANGED
@@ -7,7 +7,8 @@ module Dynflow
|
|
7
7
|
# the number of threads in the pool handling the execution
|
8
8
|
attr_accessor :pool_size
|
9
9
|
|
10
|
-
# the size of db connection pool
|
10
|
+
# the size of db connection pool, if not set, it's calculated
|
11
|
+
# from the amount of workers in the pool
|
11
12
|
attr_accessor :db_pool_size
|
12
13
|
|
13
14
|
# set true if the executor runs externally (by default true in procution, othewise false)
|
@@ -32,7 +33,6 @@ module Dynflow
|
|
32
33
|
|
33
34
|
def initialize
|
34
35
|
self.pool_size = 5
|
35
|
-
self.db_pool_size = pool_size + 5
|
36
36
|
self.remote = ::Rails.env.production?
|
37
37
|
self.transaction_adapter = ::Dynflow::TransactionAdapters::ActiveRecord.new
|
38
38
|
self.eager_load_paths = []
|
@@ -83,13 +83,24 @@ module Dynflow
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def increase_db_pool_size?
|
86
|
-
!::Rails.env.test?
|
86
|
+
!::Rails.env.test? && !remote?
|
87
|
+
end
|
88
|
+
|
89
|
+
def calculate_db_pool_size(world)
|
90
|
+
self.db_pool_size || world.config.queues.values.inject(5) do |pool_size, pool_options|
|
91
|
+
pool_size += pool_options[:pool_size]
|
92
|
+
end
|
87
93
|
end
|
88
94
|
|
89
95
|
# To avoid pottential timeouts on db connection pool, make sure
|
90
96
|
# we have the pool bigger than the thread pool
|
91
|
-
def increase_db_pool_size
|
97
|
+
def increase_db_pool_size(world = nil)
|
98
|
+
if world.nil?
|
99
|
+
warn 'Deprecated: using `increase_db_pool_size` outside of Dynflow code is not needed anymore'
|
100
|
+
return
|
101
|
+
end
|
92
102
|
if increase_db_pool_size?
|
103
|
+
db_pool_size = calculate_db_pool_size(world)
|
93
104
|
::ActiveRecord::Base.connection_pool.disconnect!
|
94
105
|
|
95
106
|
config = ::ActiveRecord::Base.configurations[::Rails.env]
|
@@ -100,11 +111,11 @@ module Dynflow
|
|
100
111
|
|
101
112
|
# generates the options hash consumable by the Dynflow's world
|
102
113
|
def world_config
|
103
|
-
::Dynflow::Config.new.tap do |config|
|
114
|
+
@world_config ||= ::Dynflow::Config.new.tap do |config|
|
104
115
|
config.auto_rescue = true
|
105
116
|
config.logger_adapter = ::Dynflow::LoggerAdapters::Delegator.new(action_logger, dynflow_logger)
|
106
117
|
config.pool_size = 5
|
107
|
-
config.persistence_adapter = initialize_persistence
|
118
|
+
config.persistence_adapter = ->(world, _) { initialize_persistence(world) }
|
108
119
|
config.transaction_adapter = transaction_adapter
|
109
120
|
config.executor = ->(world, _) { initialize_executor(world) }
|
110
121
|
config.connector = ->(world, _) { initialize_connector(world) }
|
@@ -115,13 +126,18 @@ module Dynflow
|
|
115
126
|
end
|
116
127
|
end
|
117
128
|
|
129
|
+
# expose the queues definition to Rails developers
|
130
|
+
def queues
|
131
|
+
world_config.queues
|
132
|
+
end
|
133
|
+
|
118
134
|
protected
|
119
135
|
|
120
|
-
def default_sequel_adapter_options
|
136
|
+
def default_sequel_adapter_options(world)
|
121
137
|
db_config = ::ActiveRecord::Base.configurations[::Rails.env].dup
|
122
138
|
db_config['adapter'] = db_config['adapter'].gsub(/_?makara_?/, '')
|
123
139
|
db_config['adapter'] = 'postgres' if db_config['adapter'] == 'postgresql'
|
124
|
-
db_config['max_connections'] =
|
140
|
+
db_config['max_connections'] = calculate_db_pool_size(world) if increase_db_pool_size?
|
125
141
|
|
126
142
|
if db_config['adapter'] == 'sqlite3'
|
127
143
|
db_config['adapter'] = 'sqlite'
|
@@ -139,7 +155,7 @@ module Dynflow
|
|
139
155
|
if remote?
|
140
156
|
false
|
141
157
|
else
|
142
|
-
::Dynflow::Executors::Parallel.new(world,
|
158
|
+
::Dynflow::Executors::Parallel.new(world, world.config.queues)
|
143
159
|
end
|
144
160
|
end
|
145
161
|
|
@@ -147,9 +163,13 @@ module Dynflow
|
|
147
163
|
::Dynflow::Connectors::Database.new(world)
|
148
164
|
end
|
149
165
|
|
166
|
+
def persistence_class
|
167
|
+
::Dynflow::PersistenceAdapters::Sequel
|
168
|
+
end
|
169
|
+
|
150
170
|
# Sequel adapter based on Rails app database.yml configuration
|
151
|
-
def initialize_persistence
|
152
|
-
|
171
|
+
def initialize_persistence(world)
|
172
|
+
persistence_class.new(default_sequel_adapter_options(world))
|
153
173
|
end
|
154
174
|
end
|
155
175
|
end
|
data/lib/dynflow/utils.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
module Dynflow
|
2
2
|
module Utils
|
3
3
|
|
4
|
+
def self.validate_keys!(hash, *valid_keys)
|
5
|
+
valid_keys.flatten!
|
6
|
+
unexpected_options = hash.keys - valid_keys - valid_keys.map(&:to_s)
|
7
|
+
unless unexpected_options.empty?
|
8
|
+
raise ArgumentError, "Unexpected options #{unexpected_options.inspect}. "\
|
9
|
+
"Valid keys are: #{valid_keys.map(&:inspect).join(', ')}"
|
10
|
+
end
|
11
|
+
hash
|
12
|
+
end
|
13
|
+
|
4
14
|
def self.symbolize_keys(hash)
|
5
15
|
return hash.symbolize_keys if hash.respond_to?(:symbolize_keys)
|
6
16
|
hash.reduce({}) do |new_hash, (key, value)|
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/web/console.rb
CHANGED
@@ -33,28 +33,30 @@ module Dynflow
|
|
33
33
|
end
|
34
34
|
|
35
35
|
get('/worlds') do
|
36
|
-
|
36
|
+
load_worlds
|
37
37
|
erb :worlds
|
38
38
|
end
|
39
39
|
|
40
40
|
post('/worlds/execution_status') do
|
41
|
-
|
42
|
-
@
|
41
|
+
load_worlds
|
42
|
+
@executors.each do |w|
|
43
43
|
hash = world.get_execution_status(w.data['id'], nil, 5).value!
|
44
|
-
hash
|
45
|
-
|
44
|
+
hash.each do |_queue_name, info|
|
45
|
+
info[:queue_size] = info[:execution_status].values.reduce(:+) || 0
|
46
|
+
end
|
47
|
+
w.data.update(:status => hash)
|
46
48
|
end
|
47
49
|
erb :worlds
|
48
50
|
end
|
49
51
|
|
50
52
|
post('/worlds/check') do
|
51
|
-
|
53
|
+
load_worlds
|
52
54
|
@validation_results = world.worlds_validity_check(params[:invalidate])
|
53
55
|
erb :worlds
|
54
56
|
end
|
55
57
|
|
56
58
|
post('/worlds/:id/check') do |id|
|
57
|
-
|
59
|
+
load_worlds
|
58
60
|
@validation_results = world.worlds_validity_check(params[:invalidate], id: params[:id])
|
59
61
|
erb :worlds
|
60
62
|
end
|
@@ -9,6 +9,22 @@ module Dynflow
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
+
def value_field(key, value)
|
13
|
+
strip_heredoc(<<-HTML)
|
14
|
+
<p>
|
15
|
+
<b>#{key}:</b>
|
16
|
+
#{h(value)}
|
17
|
+
</p>
|
18
|
+
HTML
|
19
|
+
end
|
20
|
+
|
21
|
+
def strip_heredoc(str)
|
22
|
+
strip_size = str.lines.reject { |l| l =~ /^\s*$/ }.map { |l| l[/^\s*/].length }.min
|
23
|
+
str.lines.map do |line|
|
24
|
+
line[strip_size..-1] || ''
|
25
|
+
end.join
|
26
|
+
end
|
27
|
+
|
12
28
|
def prettify_value(value)
|
13
29
|
YAML.dump(value)
|
14
30
|
end
|
@@ -28,6 +28,11 @@ module Dynflow
|
|
28
28
|
return @filtering_options
|
29
29
|
end
|
30
30
|
|
31
|
+
def load_worlds(executors_only = false)
|
32
|
+
@worlds = world.coordinator.find_worlds(executors_only)
|
33
|
+
@executors, @clients = @worlds.partition(&:executor?)
|
34
|
+
end
|
35
|
+
|
31
36
|
def find_execution_plans_options(show_all = false)
|
32
37
|
options = Utils.indifferent_hash({})
|
33
38
|
options.merge!(filtering_options(show_all))
|
data/lib/dynflow/world.rb
CHANGED
@@ -4,7 +4,7 @@ module Dynflow
|
|
4
4
|
include Algebrick::TypeCheck
|
5
5
|
include Algebrick::Matching
|
6
6
|
|
7
|
-
attr_reader :id, :client_dispatcher, :executor_dispatcher, :executor, :connector,
|
7
|
+
attr_reader :id, :config, :client_dispatcher, :executor_dispatcher, :executor, :connector,
|
8
8
|
:transaction_adapter, :logger_adapter, :coordinator,
|
9
9
|
:persistence, :action_classes, :subscription_index,
|
10
10
|
:middleware, :auto_rescue, :clock, :meta, :delayed_executor, :auto_validity_check, :validity_check_timeout, :throttle_limiter,
|
@@ -13,53 +13,53 @@ module Dynflow
|
|
13
13
|
def initialize(config)
|
14
14
|
@id = SecureRandom.uuid
|
15
15
|
@clock = spawn_and_wait(Clock, 'clock')
|
16
|
-
|
17
|
-
@logger_adapter =
|
18
|
-
|
19
|
-
@transaction_adapter =
|
20
|
-
@persistence = Persistence.new(self,
|
21
|
-
:backup_deleted_plans =>
|
22
|
-
:backup_dir =>
|
23
|
-
@coordinator = Coordinator.new(
|
24
|
-
@executor =
|
25
|
-
@action_classes =
|
26
|
-
@auto_rescue =
|
27
|
-
@exit_on_terminate = Concurrent::AtomicBoolean.new(
|
28
|
-
@connector =
|
16
|
+
@config = Config::ForWorld.new(config, self)
|
17
|
+
@logger_adapter = @config.logger_adapter
|
18
|
+
@config.validate
|
19
|
+
@transaction_adapter = @config.transaction_adapter
|
20
|
+
@persistence = Persistence.new(self, @config.persistence_adapter,
|
21
|
+
:backup_deleted_plans => @config.backup_deleted_plans,
|
22
|
+
:backup_dir => @config.backup_dir)
|
23
|
+
@coordinator = Coordinator.new(@config.coordinator_adapter)
|
24
|
+
@executor = @config.executor
|
25
|
+
@action_classes = @config.action_classes
|
26
|
+
@auto_rescue = @config.auto_rescue
|
27
|
+
@exit_on_terminate = Concurrent::AtomicBoolean.new(@config.exit_on_terminate)
|
28
|
+
@connector = @config.connector
|
29
29
|
@middleware = Middleware::World.new
|
30
30
|
@middleware.use Middleware::Common::Transaction if @transaction_adapter
|
31
31
|
@client_dispatcher = spawn_and_wait(Dispatcher::ClientDispatcher, "client-dispatcher", self)
|
32
|
-
@dead_letter_handler = spawn_and_wait(DeadLetterSilencer, 'default_dead_letter_handler',
|
33
|
-
@
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@throttle_limiter = config_for_world.throttle_limiter
|
32
|
+
@dead_letter_handler = spawn_and_wait(DeadLetterSilencer, 'default_dead_letter_handler', @config.silent_dead_letter_matchers)
|
33
|
+
@auto_validity_check = @config.auto_validity_check
|
34
|
+
@validity_check_timeout = @config.validity_check_timeout
|
35
|
+
@throttle_limiter = @config.throttle_limiter
|
37
36
|
@terminated = Concurrent.event
|
38
|
-
@termination_timeout =
|
37
|
+
@termination_timeout = @config.termination_timeout
|
39
38
|
calculate_subscription_index
|
40
39
|
|
41
40
|
if executor
|
42
|
-
@executor_dispatcher = spawn_and_wait(Dispatcher::ExecutorDispatcher, "executor-dispatcher", self,
|
41
|
+
@executor_dispatcher = spawn_and_wait(Dispatcher::ExecutorDispatcher, "executor-dispatcher", self, @config.executor_semaphore)
|
43
42
|
executor.initialized.wait
|
44
43
|
end
|
45
44
|
perform_validity_checks if auto_validity_check
|
46
45
|
|
47
|
-
@delayed_executor = try_spawn(
|
48
|
-
@execution_plan_cleaner = try_spawn(
|
49
|
-
@meta =
|
46
|
+
@delayed_executor = try_spawn(:delayed_executor, Coordinator::DelayedExecutorLock)
|
47
|
+
@execution_plan_cleaner = try_spawn(:execution_plan_cleaner, Coordinator::ExecutionPlanCleanerLock)
|
48
|
+
@meta = @config.meta
|
49
|
+
@meta['queues'] = @config.queues if @executor
|
50
50
|
@meta['delayed_executor'] = true if @delayed_executor
|
51
51
|
@meta['execution_plan_cleaner'] = true if @execution_plan_cleaner
|
52
52
|
coordinator.register_world(registered_world)
|
53
53
|
@termination_barrier = Mutex.new
|
54
54
|
@before_termination_hooks = Queue.new
|
55
55
|
|
56
|
-
if
|
56
|
+
if @config.auto_terminate
|
57
57
|
at_exit do
|
58
58
|
@exit_on_terminate.make_false # make sure we don't terminate twice
|
59
59
|
self.terminate.wait
|
60
60
|
end
|
61
61
|
end
|
62
|
-
self.auto_execute if
|
62
|
+
self.auto_execute if @config.auto_execute
|
63
63
|
@delayed_executor.start if @delayed_executor
|
64
64
|
end
|
65
65
|
|
@@ -402,9 +402,9 @@ module Dynflow
|
|
402
402
|
[]
|
403
403
|
end
|
404
404
|
|
405
|
-
def try_spawn(
|
405
|
+
def try_spawn(what, lock_class = nil)
|
406
406
|
object = nil
|
407
|
-
return nil if !executor || (object =
|
407
|
+
return nil if !executor || (object = @config.public_send(what)).nil?
|
408
408
|
|
409
409
|
coordinator.acquire(lock_class.new(self)) if lock_class
|
410
410
|
object.spawn.wait
|
@@ -4,6 +4,8 @@ require 'dynflow/active_job/queue_adapter'
|
|
4
4
|
|
5
5
|
module Dynflow
|
6
6
|
class SampleJob < ::ActiveJob::Base
|
7
|
+
queue_as :slow
|
8
|
+
|
7
9
|
def perform(msg)
|
8
10
|
puts "This job says #{msg}"
|
9
11
|
end
|
@@ -20,8 +22,10 @@ module Dynflow
|
|
20
22
|
rails_app_mock .expect(:dynflow, dynflow_mock)
|
21
23
|
rails_mock = Minitest::Mock.new
|
22
24
|
rails_mock.expect(:application, rails_app_mock)
|
23
|
-
|
24
|
-
|
25
|
+
if defined? ::Rails
|
26
|
+
@original_rails = ::Rails
|
27
|
+
Object.send(:remove_const, 'Rails')
|
28
|
+
end
|
25
29
|
Object.const_set('Rails', rails_mock)
|
26
30
|
end
|
27
31
|
|
data/test/test_helper.rb
CHANGED
data/test/world_test.rb
CHANGED
@@ -10,24 +10,29 @@ module Dynflow
|
|
10
10
|
describe '#meta' do
|
11
11
|
it 'by default informs about the hostname and the pid running the world' do
|
12
12
|
registered_world = world.coordinator.find_worlds(false, id: world.id).first
|
13
|
-
registered_world.meta.must_equal('hostname' => Socket.gethostname, 'pid' => Process.pid
|
13
|
+
registered_world.meta.must_equal('hostname' => Socket.gethostname, 'pid' => Process.pid,
|
14
|
+
'queues' => { 'default' => { 'pool_size' => 5 },
|
15
|
+
'slow' => { 'pool_size' => 1 }})
|
14
16
|
end
|
15
17
|
|
16
18
|
it 'is configurable' do
|
17
19
|
registered_world = world.coordinator.find_worlds(false, id: world_with_custom_meta.id).first
|
18
|
-
registered_world.meta
|
20
|
+
registered_world.meta['fast'].must_equal true
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
24
|
describe '#get_execution_status' do
|
23
25
|
let(:base) do
|
24
|
-
{ :pool_size => 5, :free_workers => 5, :execution_status => {} }
|
26
|
+
{ :default => { :pool_size => 5, :free_workers => 5, :execution_status => {} },
|
27
|
+
:slow => { :pool_size=> 1, :free_workers=> 1, :execution_status=> {}} }
|
25
28
|
end
|
26
29
|
|
27
30
|
it 'retrieves correct execution items count' do
|
28
31
|
world.get_execution_status(world.id, nil, 5).value!.must_equal(base)
|
29
32
|
id = 'something like uuid'
|
30
|
-
expected = base.
|
33
|
+
expected = base.dup
|
34
|
+
expected[:default][:execution_status] = { id => 0 }
|
35
|
+
expected[:slow][:execution_status] = { id => 0 }
|
31
36
|
world.get_execution_status(world.id, id, 5).value!.must_equal(expected)
|
32
37
|
end
|
33
38
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<% validation_result = @validation_results[world.id] if @validation_results %>
|
2
|
+
|
3
|
+
<% unless validation_result == :invalidated %>
|
4
|
+
<a href="<%= url("/worlds/#{world.id}/check") %>" class="postlink">check</a>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<% if validation_result %>
|
8
|
+
<% if validation_result == :invalid %>
|
9
|
+
<a href="<%= url("/worlds/#{world.id}/check?invalidate=true") %>" class="postlink">invalidate</a>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<span class="label label-<%= validation_result_css_class(validation_result) %>">
|
13
|
+
<%= h(validation_result) %>
|
14
|
+
</span>
|
15
|
+
<% end %>
|
data/web/views/worlds.erb
CHANGED
@@ -6,41 +6,48 @@
|
|
6
6
|
<li><a href="<%= url("/worlds/execution_status") %>" class="postlink">load execution items counts</a>: see counts of execution items per world</li>
|
7
7
|
</ul>
|
8
8
|
|
9
|
+
<h3>Executors</h3>
|
10
|
+
<% @executors.each do |world| %>
|
11
|
+
<%= value_field('Id', world.id) %>
|
12
|
+
<%= value_field('Metadata', world.meta) %>
|
13
|
+
<p>
|
14
|
+
<b>Status:</b>
|
15
|
+
<%= erb :world_validation_result, locals: { world: world } %>
|
16
|
+
</p>
|
17
|
+
<% if world.data[:status] %>
|
18
|
+
<table class="table">
|
19
|
+
<thead>
|
20
|
+
<tr>
|
21
|
+
<th>Queue name</th>
|
22
|
+
<th>Queue size</th>
|
23
|
+
<th>Free/Total workers</th>
|
24
|
+
</tr>
|
25
|
+
</thead>
|
26
|
+
<% world.data[:status].each do |queue_name, info| %>
|
27
|
+
<tr>
|
28
|
+
<td><%= h(queue_name) %></td>
|
29
|
+
<td><%= h(info[:queue_size]) %></td>
|
30
|
+
<td><%= h(info[:free_workers]) %>/<%= h(info[:pool_size]) %></td>
|
31
|
+
</tr>
|
32
|
+
<% end %>
|
33
|
+
</table>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
<h3>Clients</h3>
|
9
37
|
<table class="table">
|
10
38
|
<thead>
|
11
39
|
<tr>
|
12
40
|
<th>Id</th>
|
13
41
|
<th>Meta</th>
|
14
|
-
<th>Executor?</th>
|
15
|
-
<th>Execution items</th>
|
16
|
-
<th>Free/Total workers</th>
|
17
42
|
<th></th>
|
18
43
|
</tr>
|
19
44
|
</thead>
|
20
|
-
|
21
|
-
<% @worlds.each do |world| %>
|
45
|
+
<% @clients.each do |world| %>
|
22
46
|
<tr>
|
23
47
|
<td><%= h(world.id) %></td>
|
24
48
|
<td><%= h(world.meta) %></td>
|
25
|
-
<td><%= "true" if world.is_a? Dynflow::Coordinator::ExecutorWorld %></td>
|
26
|
-
<td><%= h(world.data['execution_status'] || 'N/A') %></td>
|
27
|
-
<td><%= world.data.key?('free_workers') ? "#{world.data['free_workers']}/#{world.data[:pool_size]}" : 'N/A' %></td>
|
28
49
|
<td>
|
29
|
-
|
30
|
-
|
31
|
-
<% unless validation_result == :invalidated %>
|
32
|
-
<a href="<%= url("/worlds/#{world.id}/check") %>" class="postlink">check</a>
|
33
|
-
<% end %>
|
34
|
-
|
35
|
-
<% if validation_result %>
|
36
|
-
<% if validation_result == :invalid %>
|
37
|
-
<a href="<%= url("/worlds/#{world.id}/check?invalidate=true") %>" class="postlink">invalidate</a>
|
38
|
-
<% end %>
|
39
|
-
|
40
|
-
<span class="label label-<%= validation_result_css_class(validation_result) %>">
|
41
|
-
<%= h(validation_result) %>
|
42
|
-
</span>
|
43
|
-
<% end %>
|
50
|
+
<%= erb :world_validation_result, locals: { world: world } %>
|
44
51
|
</td>
|
45
52
|
</tr>
|
46
53
|
<% end %>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Necas
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-03-
|
12
|
+
date: 2018-03-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -482,6 +482,7 @@ files:
|
|
482
482
|
- lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb
|
483
483
|
- lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb
|
484
484
|
- lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb
|
485
|
+
- lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb
|
485
486
|
- lib/dynflow/rails.rb
|
486
487
|
- lib/dynflow/rails/configuration.rb
|
487
488
|
- lib/dynflow/rails/daemon.rb
|
@@ -579,6 +580,7 @@ files:
|
|
579
580
|
- web/views/layout.erb
|
580
581
|
- web/views/plan_step.erb
|
581
582
|
- web/views/show.erb
|
583
|
+
- web/views/world_validation_result.erb
|
582
584
|
- web/views/worlds.erb
|
583
585
|
homepage: http://github.com/Dynflow/dynflow
|
584
586
|
licenses:
|