foreman-tasks 0.9.1 → 0.9.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 +4 -4
- data/Gemfile +2 -1
- data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +1 -1
- data/app/lib/actions/bulk_action.rb +0 -1
- data/app/lib/actions/middleware/keep_current_user.rb +1 -1
- data/app/models/foreman_tasks/concerns/action_triggering.rb +7 -7
- data/app/models/foreman_tasks/task.rb +7 -5
- data/app/models/foreman_tasks/task/dynflow_task.rb +40 -12
- data/app/views/foreman_tasks/tasks/_errors.html.erb +5 -1
- data/bin/dynflow-executor +53 -28
- data/deploy/foreman-tasks.sysconfig +12 -0
- data/foreman-tasks.gemspec +2 -1
- data/lib/foreman_tasks/dynflow.rb +10 -2
- data/lib/foreman_tasks/dynflow/daemon.rb +90 -19
- data/lib/foreman_tasks/version.rb +1 -1
- data/locale/en/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/en/foreman_tasks.po +6 -9
- data/locale/foreman_tasks.pot +102 -107
- data/test/unit/actions/action_with_sub_plans_test.rb +1 -3
- data/test/unit/config/environment.rb +1 -0
- data/test/unit/daemon_test.rb +86 -0
- data/test/unit/task_test.rb +19 -1
- metadata +24 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffd1f0c3a1276172b47fc40f58b34d92641fe2b1
|
4
|
+
data.tar.gz: e9faaecd202c22b32c13de8de2ba8bea5ddaee49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f057fff909d5f4cfc7a107f91dcba2654618dca2615fa23b819e0cbe6867f6ad7cdaae9b4de8bbbde832c9783d8c0b1d729151a9a7dacefdb42d76c15f5e597
|
7
|
+
data.tar.gz: 3f306778e7796e788f5f251bd2c4be7ef31ae94094a6b29d9ab7e1972650eae9c0c9c85590ae10d173be2b46101cb2c7d77da43cdaed09de038db478246f00b0
|
data/Gemfile
CHANGED
@@ -3,7 +3,8 @@ source "https://rubygems.org"
|
|
3
3
|
# Declare your gem's dependencies in tasks.gemspec.
|
4
4
|
# Bundler will treat runtime dependencies like base dependencies, and
|
5
5
|
# development dependencies will be added by default to the :development group.
|
6
|
-
|
6
|
+
|
7
|
+
gemspec :name => 'foreman-tasks'
|
7
8
|
|
8
9
|
# Declare any dependencies that are still in development here instead of in
|
9
10
|
# your gemspec. These might include edge Rails or gems from your path or
|
@@ -116,7 +116,7 @@ module ForemanTasks
|
|
116
116
|
tags = []
|
117
117
|
tags << text_f(f, :start_at_raw, :label => _('Start at'), :placeholder => 'YYYY-mm-dd HH:MM')
|
118
118
|
tags << text_f(f, :start_before_raw, :label => _('Start before'), :placeholder => 'YYYY-mm-dd HH:MM',
|
119
|
-
|
119
|
+
:label_help => _('Indicates that the action should be cancelled if it cannot be started before this time.'))
|
120
120
|
content_tag(:fieldset, nil, :id => 'trigger_mode_future', :class => "trigger_mode_form #{'hidden' unless triggering.future?}") do
|
121
121
|
tags.join.html_safe
|
122
122
|
end
|
@@ -108,13 +108,13 @@ module ForemanTasks
|
|
108
108
|
@_dynflow_task_wrapped = true
|
109
109
|
|
110
110
|
@_dynflow_hook_action = case method
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
111
|
+
when :save
|
112
|
+
new_record? ? create_action : update_action
|
113
|
+
when :destroy
|
114
|
+
destroy_action
|
115
|
+
else
|
116
|
+
raise 'unexpected method'
|
117
|
+
end
|
118
118
|
ensure_not_in_transaction! if @_dynflow_hook_action
|
119
119
|
yield.tap do |result|
|
120
120
|
execute_planned_action if result
|
@@ -38,10 +38,10 @@ module ForemanTasks
|
|
38
38
|
scoped_search :on => :parent_task_id, :complete_value => true
|
39
39
|
scoped_search :relation => :locks, :on => :resource_type, :complete_value => true, :rename => 'resource_type', :ext_method => :search_by_generic_resource
|
40
40
|
scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'resource_id', :ext_method => :search_by_generic_resource
|
41
|
-
scoped_search :relation => :owners,
|
42
|
-
:on => :id,
|
43
|
-
:complete_value => true,
|
44
|
-
:rename => 'owner.id',
|
41
|
+
scoped_search :relation => :owners,
|
42
|
+
:on => :id,
|
43
|
+
:complete_value => true,
|
44
|
+
:rename => 'owner.id',
|
45
45
|
:ext_method => :search_by_owner,
|
46
46
|
:validator => ->(value) { ScopedSearch::Validators::INTEGER.call(value) || value == 'current_user' }
|
47
47
|
scoped_search :relation => :owners, :on => :login, :complete_value => true, :rename => 'owner.login', :ext_method => :search_by_owner
|
@@ -172,7 +172,9 @@ module ForemanTasks
|
|
172
172
|
end
|
173
173
|
|
174
174
|
def sub_tasks_counts
|
175
|
-
result = %w(cancelled error pending success warning).
|
175
|
+
result = %w(cancelled error pending success warning).inject({}) do |hash, state|
|
176
|
+
hash.update(state => 0)
|
177
|
+
end
|
176
178
|
result.update sub_tasks.group(:result).count
|
177
179
|
sum = result.values.reduce(:+)
|
178
180
|
if respond_to?(:main_action) && main_action.respond_to?(:total_count)
|
@@ -2,7 +2,6 @@ module ForemanTasks
|
|
2
2
|
class Task::DynflowTask < ForemanTasks::Task
|
3
3
|
include Algebrick::TypeCheck
|
4
4
|
|
5
|
-
delegate :cancellable?, :progress, to: :execution_plan
|
6
5
|
scope :for_action, ->(action_class) { where(label: action_class.name) }
|
7
6
|
|
8
7
|
def update_from_dynflow(data)
|
@@ -19,26 +18,50 @@ module ForemanTasks
|
|
19
18
|
DynflowTask.where(:external_id => main_action.caller_execution_plan_id).first!.id
|
20
19
|
end
|
21
20
|
end
|
22
|
-
self.label ||= main_action.class.name
|
21
|
+
self.label ||= main_action && main_action.class.name
|
23
22
|
changes = self.changes
|
24
23
|
save!
|
25
24
|
changes
|
26
25
|
end
|
27
26
|
|
27
|
+
def cancellable?
|
28
|
+
execution_plan.try(:cancellable?)
|
29
|
+
end
|
30
|
+
|
28
31
|
def cancel
|
29
|
-
execution_plan
|
32
|
+
execution_plan!.cancel.any?
|
30
33
|
end
|
31
34
|
|
32
35
|
def resumable?
|
33
|
-
execution_plan.state == :paused
|
36
|
+
execution_plan.try(:state) == :paused
|
34
37
|
end
|
35
38
|
|
36
39
|
def cancellable_action?(action)
|
37
40
|
action.is_a?(::Dynflow::Action::Cancellable)
|
38
41
|
end
|
39
42
|
|
40
|
-
def
|
41
|
-
|
43
|
+
def progress
|
44
|
+
execution_plan.try(:progress) || 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def execution_plan(silence_exception = true)
|
48
|
+
return @execution_plan if defined?(@execution_plan)
|
49
|
+
execution_plan = ForemanTasks.dynflow.world.persistence.load_execution_plan(external_id)
|
50
|
+
# don't use invalid execution plans for our purposes
|
51
|
+
if execution_plan.respond_to?(:valid?) && !execution_plan.valid?
|
52
|
+
raise execution_plan.exception
|
53
|
+
else
|
54
|
+
@execution_plan = execution_plan
|
55
|
+
end
|
56
|
+
@execution_plan
|
57
|
+
rescue => e
|
58
|
+
Foreman::Logging.exception("Could not load execution plan #{external_id} for task #{id}", e, :logger => 'foreman-tasks')
|
59
|
+
raise e unless silence_exception
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def execution_plan!
|
64
|
+
execution_plan(false)
|
42
65
|
end
|
43
66
|
|
44
67
|
def input
|
@@ -50,11 +73,11 @@ module ForemanTasks
|
|
50
73
|
end
|
51
74
|
|
52
75
|
def failed_steps
|
53
|
-
execution_plan.
|
76
|
+
execution_plan.try(:steps_in_state, :skipped, :skipping, :error) || []
|
54
77
|
end
|
55
78
|
|
56
79
|
def running_steps
|
57
|
-
execution_plan.
|
80
|
+
execution_plan.try(:steps_in_state, :running, :suspended) || []
|
58
81
|
end
|
59
82
|
|
60
83
|
def humanized
|
@@ -69,8 +92,8 @@ module ForemanTasks
|
|
69
92
|
end
|
70
93
|
|
71
94
|
def main_action
|
72
|
-
return @main_action if @main_action
|
73
|
-
execution_plan.root_plan_step.action execution_plan
|
95
|
+
return @main_action if defined?(@main_action)
|
96
|
+
@main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
|
74
97
|
end
|
75
98
|
|
76
99
|
def get_humanized(method)
|
@@ -79,7 +102,8 @@ module ForemanTasks
|
|
79
102
|
method = "humanized_#{method}".to_sym
|
80
103
|
end
|
81
104
|
Match! method, :humanized_name, :humanized_input, :humanized_output, :humanized_errors
|
82
|
-
return N_('N/A') if method != :humanized_name && main_action.execution_plan.state == :scheduled
|
105
|
+
return N_('N/A') if method != :humanized_name && (main_action.nil? || main_action.execution_plan.state == :scheduled)
|
106
|
+
return N_(label) if method == :humanized_name && main_action.nil?
|
83
107
|
@humanized_cache[method] ||= begin
|
84
108
|
if main_action.respond_to? method
|
85
109
|
begin
|
@@ -102,6 +126,10 @@ module ForemanTasks
|
|
102
126
|
logger.warn('Task %s updated at consistency check: %s' % [task.id, changes.inspect])
|
103
127
|
end
|
104
128
|
rescue => e
|
129
|
+
# if we fail updating the data from dynflow, it usually means there is something
|
130
|
+
# odd with the data consistency and at this point it is not possible to resume, switching
|
131
|
+
# the task to stopped/error
|
132
|
+
task.update_attributes(:state => 'stopped', :result => 'error')
|
105
133
|
Foreman::Logging.exception("Failed at consistency check for task #{task.id}", e, :logger => 'foreman-tasks')
|
106
134
|
end
|
107
135
|
end
|
@@ -126,7 +154,7 @@ module ForemanTasks
|
|
126
154
|
end
|
127
155
|
|
128
156
|
def cancelled?
|
129
|
-
execution_plan.errors.
|
157
|
+
execution_plan.errors.any? { |error| error.exception_class == ::ForemanTasks::Task::TaskCancelledException }
|
130
158
|
end
|
131
159
|
end
|
132
160
|
end
|
@@ -1,7 +1,11 @@
|
|
1
1
|
<% if @task.is_a? ForemanTasks::Task::DynflowTask %>
|
2
2
|
<div>
|
3
3
|
<% failed_steps = @task.failed_steps %>
|
4
|
-
<% if
|
4
|
+
<% if @task.execution_plan.nil? %>
|
5
|
+
<div class="alert alert-danger">
|
6
|
+
<%= _("Execution plan data not available ") %>
|
7
|
+
</div>
|
8
|
+
<% elsif failed_steps.empty? %>
|
5
9
|
<div class="alert alert-success">
|
6
10
|
<%= _("No errors") %>
|
7
11
|
</div>
|
data/bin/dynflow-executor
CHANGED
@@ -2,45 +2,70 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
|
5
|
-
|
5
|
+
class ArgvParser
|
6
|
+
attr_reader :options, :command
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
def initialize(argv, file)
|
9
|
+
@options = { foreman_root: Dir.pwd }
|
10
|
+
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
opts.banner = banner(file)
|
13
|
+
|
14
|
+
opts.on('-h', '--help', 'Show this message') do
|
15
|
+
puts opts
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
opts.on('-f', '--foreman-root=PATH', "Path to Foreman Rails root path. By default '#{@options[:foreman_root]}'") do |path|
|
19
|
+
@options[:foreman_root] = path
|
20
|
+
end
|
21
|
+
opts.on('-c', '--executors-count=COUNT', 'Number of parallel executors to spawn. Overrides EXECUTORS_COUNT environment varaible.') do |count|
|
22
|
+
@options[:executors_count] = count.to_i
|
23
|
+
end
|
24
|
+
opts.on('-m', '--memory-limit=SIZE', 'Limits the amount of memory an executor can consume. Overrides EXECUTOR_MEMORY_LIMIT environment varaible. You can use kb, mb, gb') do |size|
|
25
|
+
@options[:memory_limit] = size
|
26
|
+
end
|
27
|
+
opts.on('--executor-memory-init-delay=SECONDS', 'Start memory polling after SECONDS. Overrides EXECUTOR_MEMORY_MONITOR_DELAY environment varaible.') do |seconds|
|
28
|
+
@options[:memory_init_delay] = seconds.to_i
|
29
|
+
end
|
30
|
+
opts.on('--executor-memory-polling-interval=SECONDS', 'Check for memory useage every SECONDS sec. Overrides EXECUTOR_MEMORY_MONITOR_INTERVAL environment varaible.') do |seconds|
|
31
|
+
@options[:memory_polling_interval] = seconds.to_i
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
args = opts.parse!(argv)
|
36
|
+
@command = args.first || 'run'
|
37
|
+
end
|
38
|
+
|
39
|
+
def banner(file)
|
40
|
+
banner = <<BANNER
|
9
41
|
Run Dynflow executor for Foreman tasks.
|
10
42
|
|
11
|
-
Usage: #{File.basename(
|
43
|
+
Usage: #{File.basename(file)} [options] ACTION"
|
12
44
|
|
13
45
|
ACTION can be one of:
|
14
46
|
|
15
|
-
|
16
|
-
|
47
|
+
* start - start the executor on background. It creates these files
|
48
|
+
in tmp/pid directory:
|
17
49
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
50
|
+
* dynflow_executor_monitor.pid - pid of monitor ensuring
|
51
|
+
the executor keeps running
|
52
|
+
* dynflow_executor.pid - pid of the executor itself
|
53
|
+
* dynflow_executor.output - stdout of the executor
|
54
|
+
* stop - stops the running executor
|
55
|
+
* restart - restarts the running executor
|
56
|
+
* run - run the executor in foreground
|
25
57
|
|
26
58
|
BANNER
|
27
|
-
|
28
|
-
opts.on('-h', '--help', 'Show this message') do
|
29
|
-
puts opts
|
30
|
-
exit 1
|
31
|
-
end
|
32
|
-
opts.on('-f', '--foreman-root=PATH', "Path to Foreman Rails root path. By default '#{options[:foreman_root]}'") do |path|
|
33
|
-
options[:foreman_root] = path
|
34
|
-
end
|
35
|
-
opts.on('-c', '--executors-count=COUNT', 'Number of parallel executors to spawn. Overrides EXECUTORS_COUNT environment varaible.') do |count|
|
36
|
-
options[:executors_count] = count.to_i
|
59
|
+
banner
|
37
60
|
end
|
38
61
|
end
|
39
62
|
|
40
|
-
|
41
|
-
|
63
|
+
# run the script if it's executed explicitly
|
64
|
+
if $PROGRAM_NAME == __FILE__
|
65
|
+
parser = ArgvParser.new(ARGV, $PROGRAM_NAME)
|
42
66
|
|
43
|
-
app_file = File.expand_path('./config/application', options[:foreman_root])
|
44
|
-
require app_file
|
67
|
+
app_file = File.expand_path('./config/application', parser.options[:foreman_root])
|
68
|
+
require app_file
|
45
69
|
|
46
|
-
ForemanTasks::Dynflow::Daemon.new.run_background(command, options)
|
70
|
+
ForemanTasks::Dynflow::Daemon.new.run_background(parser.command, parser.options)
|
71
|
+
end
|
@@ -12,3 +12,15 @@ RUBY_GC_MALLOC_LIMIT_MAX=16000100
|
|
12
12
|
RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=1.1
|
13
13
|
RUBY_GC_OLDMALLOC_LIMIT=16000100
|
14
14
|
RUBY_GC_OLDMALLOC_LIMIT_MAX=16000100
|
15
|
+
|
16
|
+
#Set the number of executors you want to run
|
17
|
+
#EXECUTORS_COUNT=1
|
18
|
+
|
19
|
+
#Set memory limit for executor process, before it's restarted automatically
|
20
|
+
#EXECUTOR_MEMORY_LIMIT=2gb
|
21
|
+
|
22
|
+
#Set delay before first memory polling to let executor initialize (in sec)
|
23
|
+
#EXECUTOR_MEMORY_MONITOR_DELAY=7200 #default: 2 hours
|
24
|
+
|
25
|
+
#Set memory polling interval, process memory will be checked every N seconds.
|
26
|
+
#EXECUTOR_MEMORY_MONITOR_INTERVAL=60
|
data/foreman-tasks.gemspec
CHANGED
@@ -29,9 +29,10 @@ DESC
|
|
29
29
|
s.extra_rdoc_files = Dir['README*', 'LICENSE']
|
30
30
|
|
31
31
|
s.add_dependency "foreman-tasks-core"
|
32
|
-
s.add_dependency "dynflow", '~> 0.8.
|
32
|
+
s.add_dependency "dynflow", '~> 0.8.24'
|
33
33
|
s.add_dependency "sequel" # for Dynflow process persistence
|
34
34
|
s.add_dependency "sinatra" # for Dynflow web console
|
35
35
|
s.add_dependency "daemons" # for running remote executor
|
36
36
|
s.add_dependency "parse-cron", '~> 0.1.4'
|
37
|
+
s.add_dependency "get_process_mem" # for memory polling
|
37
38
|
end
|
@@ -9,8 +9,9 @@ module ForemanTasks
|
|
9
9
|
require 'foreman_tasks/dynflow/daemon'
|
10
10
|
require 'foreman_tasks/dynflow/console_authorizer'
|
11
11
|
|
12
|
-
def initialize
|
12
|
+
def initialize(world_class = nil)
|
13
13
|
@required = false
|
14
|
+
@world_class = world_class
|
14
15
|
end
|
15
16
|
|
16
17
|
def config
|
@@ -37,7 +38,7 @@ module ForemanTasks
|
|
37
38
|
if config.lazy_initialization && defined?(PhusionPassenger)
|
38
39
|
config.dynflow_logger.warn('ForemanTasks: lazy loading with PhusionPassenger might lead to unexpected results')
|
39
40
|
end
|
40
|
-
|
41
|
+
init_world.tap do |world|
|
41
42
|
@world = world
|
42
43
|
|
43
44
|
unless config.remote?
|
@@ -119,5 +120,12 @@ module ForemanTasks
|
|
119
120
|
def loaded_paths
|
120
121
|
@loaded_paths ||= Set.new
|
121
122
|
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def init_world
|
127
|
+
return config.initialize_world(@world_class) if @world_class
|
128
|
+
config.initialize_world
|
129
|
+
end
|
122
130
|
end
|
123
131
|
end
|
@@ -1,18 +1,47 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'daemons'
|
3
|
+
require 'get_process_mem'
|
4
|
+
require 'dynflow/watchers/memory_consumption_watcher'
|
2
5
|
|
3
6
|
module ForemanTasks
|
4
7
|
class Dynflow::Daemon
|
8
|
+
attr_reader :dynflow_memory_watcher_class,
|
9
|
+
:daemons_class
|
10
|
+
|
11
|
+
# make Daemon dependency injection ready for testing purposes
|
12
|
+
def initialize(
|
13
|
+
dynflow_memory_watcher_class = ::Dynflow::Watchers::MemoryConsumptionWatcher,
|
14
|
+
daemons_class = ::Daemons
|
15
|
+
)
|
16
|
+
@dynflow_memory_watcher_class = dynflow_memory_watcher_class
|
17
|
+
@daemons_class = daemons_class
|
18
|
+
end
|
19
|
+
|
5
20
|
# load the Rails environment and initialize the executor
|
6
21
|
# in this thread.
|
7
|
-
def run(foreman_root = Dir.pwd)
|
22
|
+
def run(foreman_root = Dir.pwd, options = {})
|
8
23
|
STDERR.puts('Starting Rails environment')
|
9
24
|
foreman_env_file = File.expand_path('./config/environment.rb', foreman_root)
|
10
25
|
unless File.exist?(foreman_env_file)
|
11
26
|
raise "#{foreman_root} doesn't seem to be a foreman root directory"
|
12
27
|
end
|
28
|
+
|
29
|
+
STDERR.puts("Starting dynflow with the following options: #{options}")
|
30
|
+
|
13
31
|
ForemanTasks.dynflow.executor!
|
32
|
+
|
33
|
+
if options[:memory_limit] > 0
|
34
|
+
ForemanTasks.dynflow.config.on_init do |world|
|
35
|
+
memory_watcher = initialize_memory_watcher(world, options[:memory_limit], options)
|
36
|
+
world.terminated.on_completion do
|
37
|
+
STDERR.puts("World has been terminated")
|
38
|
+
memory_watcher = nil # the object can be disposed
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
14
43
|
require foreman_env_file
|
15
|
-
STDERR.puts(
|
44
|
+
STDERR.puts("Everything ready for world: #{(ForemanTasks.dynflow.initialized? ? ForemanTasks.dynflow.world.id : nil)}")
|
16
45
|
sleep
|
17
46
|
ensure
|
18
47
|
STDERR.puts('Exiting')
|
@@ -20,13 +49,6 @@ module ForemanTasks
|
|
20
49
|
|
21
50
|
# run the executor as a daemon
|
22
51
|
def run_background(command = 'start', options = {})
|
23
|
-
default_options = { foreman_root: Dir.pwd,
|
24
|
-
process_name: 'dynflow_executor',
|
25
|
-
pid_dir: "#{Rails.root}/tmp/pids",
|
26
|
-
log_dir: File.join(Rails.root, 'log'),
|
27
|
-
wait_attempts: 300,
|
28
|
-
wait_sleep: 1,
|
29
|
-
executors_count: (ENV['EXECUTORS_COUNT'] || 1).to_i }
|
30
52
|
options = default_options.merge(options)
|
31
53
|
FileUtils.mkdir_p(options[:pid_dir])
|
32
54
|
begin
|
@@ -42,18 +64,13 @@ module ForemanTasks
|
|
42
64
|
STDERR.puts("Dynflow Executor: #{command} in progress")
|
43
65
|
|
44
66
|
options[:executors_count].times do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
:dir_mode => :normal,
|
50
|
-
:monitor => true,
|
51
|
-
:log_output => true,
|
52
|
-
:log_output_syslog => true,
|
53
|
-
:ARGV => [command]) do |*_args|
|
67
|
+
daemons_class.run_proc(
|
68
|
+
options[:process_name],
|
69
|
+
daemons_options(command, options)
|
70
|
+
) do |*_args|
|
54
71
|
begin
|
55
72
|
::Logging.reopen
|
56
|
-
run(options[:foreman_root])
|
73
|
+
run(options[:foreman_root], options)
|
57
74
|
rescue => e
|
58
75
|
STDERR.puts e.message
|
59
76
|
Foreman::Logging.exception('Failed running foreman-tasks daemon', e)
|
@@ -68,5 +85,59 @@ module ForemanTasks
|
|
68
85
|
def world
|
69
86
|
ForemanTasks.dynflow.world
|
70
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def daemons_options(command, options)
|
92
|
+
{
|
93
|
+
:multiple => true,
|
94
|
+
:dir => options[:pid_dir],
|
95
|
+
:log_dir => options[:log_dir],
|
96
|
+
:dir_mode => :normal,
|
97
|
+
:monitor => true,
|
98
|
+
:log_output => true,
|
99
|
+
:log_output_syslog => true,
|
100
|
+
:monitor_interval => [options[:memory_polling_interval] / 2, 30].min,
|
101
|
+
:ARGV => [command]
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def default_options
|
106
|
+
{
|
107
|
+
foreman_root: Dir.pwd,
|
108
|
+
process_name: 'dynflow_executor',
|
109
|
+
pid_dir: "#{Rails.root}/tmp/pids",
|
110
|
+
log_dir: File.join(Rails.root, 'log'),
|
111
|
+
wait_attempts: 300,
|
112
|
+
wait_sleep: 1,
|
113
|
+
executors_count: (ENV['EXECUTORS_COUNT'] || 1).to_i,
|
114
|
+
memory_limit: begin
|
115
|
+
(ENV['EXECUTOR_MEMORY_LIMIT'] || '').to_gb.gigabytes
|
116
|
+
rescue RuntimeError
|
117
|
+
ENV['EXECUTOR_MEMORY_LIMIT'].to_i
|
118
|
+
end,
|
119
|
+
memory_init_delay: (ENV['EXECUTOR_MEMORY_MONITOR_DELAY'] || 7200).to_i, # 2 hours
|
120
|
+
memory_polling_interval: (ENV['EXECUTOR_MEMORY_MONITOR_INTERVAL'] || 60).to_i
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize_memory_watcher(world, memory_limit, options)
|
125
|
+
watcher_options = {}
|
126
|
+
watcher_options[:polling_interval] = options[:memory_polling_interval]
|
127
|
+
watcher_options[:initial_wait] = options[:memory_init_delay]
|
128
|
+
watcher_options[:memory_checked_callback] = ->(current_memory, memory_limit) { log_memory_within_limit(current_memory, memory_limit) }
|
129
|
+
watcher_options[:memory_limit_exceeded_callback] = ->(current_memory, memory_limit) { log_memory_limit_exceeded(current_memory, memory_limit) }
|
130
|
+
dynflow_memory_watcher_class.new(world, memory_limit, watcher_options)
|
131
|
+
end
|
132
|
+
|
133
|
+
def log_memory_limit_exceeded(current_memory, memory_limit)
|
134
|
+
message = "Memory level exceeded, registered #{current_memory} bytes, which is greater than #{memory_limit} limit."
|
135
|
+
Foreman::Logging.logger('foreman-tasks').error(message)
|
136
|
+
end
|
137
|
+
|
138
|
+
def log_memory_within_limit(current_memory, memory_limit)
|
139
|
+
message = "Memory level OK, registered #{current_memory} bytes, which is less than #{memory_limit} limit."
|
140
|
+
Foreman::Logging.logger('foreman-tasks').debug(message)
|
141
|
+
end
|
71
142
|
end
|
72
143
|
end
|