foreman-tasks 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fae15bedc8d294cc8486d2d99b0843d53840d1e6
4
- data.tar.gz: af614f0e462ca233cc5615f2f47793caa184f380
3
+ metadata.gz: ffd1f0c3a1276172b47fc40f58b34d92641fe2b1
4
+ data.tar.gz: e9faaecd202c22b32c13de8de2ba8bea5ddaee49
5
5
  SHA512:
6
- metadata.gz: bd813948f1ca5b2305e06dd090e631bd1c598f8b83e52a37ae7e50071e9cfd31a73ae52cec3ed0822033089ead74109d222adc35b718d4e29021536264ef2cce
7
- data.tar.gz: 9ce2326641602979ada0e7c56dc957923eb579791ceee0c74b0e06346d2007a792472e5cc9d216ec59f65576427adcfaab276b3023995e045410016554dbcf39
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
- gemspec
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
- :label_help => _('Indicates that the action should be cancelled if it cannot be started before this time.'))
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
@@ -1,6 +1,5 @@
1
1
  module Actions
2
2
  class BulkAction < Actions::ActionWithSubPlans
3
-
4
3
  include Dynflow::Action::WithBulkSubPlans
5
4
 
6
5
  # == Parameters:
@@ -47,7 +47,7 @@ module Actions
47
47
  end
48
48
 
49
49
  def restore_curent_user
50
- User.current = User.find(action.input[:current_user_id])
50
+ User.current = User.unscoped.find(action.input[:current_user_id])
51
51
  yield
52
52
  ensure
53
53
  User.current = nil
@@ -108,13 +108,13 @@ module ForemanTasks
108
108
  @_dynflow_task_wrapped = true
109
109
 
110
110
  @_dynflow_hook_action = case method
111
- when :save
112
- new_record? ? create_action : update_action
113
- when :destroy
114
- destroy_action
115
- else
116
- raise 'unexpected method'
117
- end
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).zip([0].cycle).to_h
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.cancel.any?
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 execution_plan
41
- @execution_plan ||= ForemanTasks.dynflow.world.persistence.load_execution_plan(external_id)
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.steps_in_state(:skipped, :skipping, :error)
76
+ execution_plan.try(:steps_in_state, :skipped, :skipping, :error) || []
54
77
  end
55
78
 
56
79
  def running_steps
57
- execution_plan.steps_in_state(:running, :suspended)
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.map(&:exception).any? { |exception| exception.class == ::ForemanTasks::Task::TaskCancelledException }
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 failed_steps.empty? %>
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
- options = { foreman_root: Dir.pwd }
5
+ class ArgvParser
6
+ attr_reader :options, :command
6
7
 
7
- opts = OptionParser.new do |opts|
8
- opts.banner = <<BANNER
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($PROGRAM_NAME)} [options] ACTION"
43
+ Usage: #{File.basename(file)} [options] ACTION"
12
44
 
13
45
  ACTION can be one of:
14
46
 
15
- * start - start the executor on background. It creates these files
16
- in tmp/pid directory:
47
+ * start - start the executor on background. It creates these files
48
+ in tmp/pid directory:
17
49
 
18
- * dynflow_executor_monitor.pid - pid of monitor ensuring
19
- the executor keeps running
20
- * dynflow_executor.pid - pid of the executor itself
21
- * dynflow_executor.output - stdout of the executor
22
- * stop - stops the running executor
23
- * restart - restarts the running executor
24
- * run - run the executor in foreground
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
- args = opts.parse!(ARGV)
41
- command = args.first || 'run'
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
@@ -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.22'
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
- config.initialize_world.tap do |world|
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('Everything ready')
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
- Daemons.run_proc(options[:process_name],
46
- :multiple => true,
47
- :dir => options[:pid_dir],
48
- :log_dir => options[:log_dir],
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