1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
require_relative '../puppet/application'
require_relative '../puppet/error'
require_relative '../puppet/util/at_fork'
require 'timeout'
# A general class for triggering a run of another
# class.
class Puppet::Agent
require_relative 'agent/locker'
include Puppet::Agent::Locker
require_relative 'agent/disabler'
include Puppet::Agent::Disabler
require_relative '../puppet/util/splayer'
include Puppet::Util::Splayer
# Special exception class used to signal an agent run has timed out.
class RunTimeoutError < Exception
end
attr_reader :client_class, :client, :should_fork
def initialize(client_class, should_fork=true)
@should_fork = can_fork? && should_fork
@client_class = client_class
end
def can_fork?
Puppet.features.posix? && RUBY_PLATFORM != 'java'
end
def needing_restart?
Puppet::Application.restart_requested?
end
# Perform a run with our client.
def run(client_options = {})
if disabled?
log_disabled_message
return
end
result = nil
wait_for_lock_deadline = nil
block_run = Puppet::Application.controlled_run do
# splay may sleep for awhile when running onetime! If not onetime, then
# the job scheduler splays (only once) so that agents assign themselves a
# slot within the splay interval.
do_splay = client_options.fetch(:splay, Puppet[:splay])
if do_splay
splay(do_splay)
if disabled?
log_disabled_message
break
end
end
# waiting for certs may sleep for awhile depending on onetime, waitforcert and maxwaitforcert!
# this needs to happen before forking so that if we fail to obtain certs and try to exit, then
# we exit the main process and not the forked child.
ssl_context = wait_for_certificates(client_options)
result = run_in_fork(should_fork) do
with_client(client_options[:transaction_uuid], client_options[:job_id]) do |client|
client_args = client_options.merge(:pluginsync => Puppet::Configurer.should_pluginsync?)
begin
# lock may sleep for awhile depending on waitforlock and maxwaitforlock!
lock do
if disabled?
log_disabled_message
nil
else
# NOTE: Timeout is pretty heinous as the location in which it
# throws an error is entirely unpredictable, which means that
# it can interrupt code blocks that perform cleanup or enforce
# sanity. The only thing a Puppet agent should do after this
# error is thrown is die with as much dignity as possible.
Timeout.timeout(Puppet[:runtimeout], RunTimeoutError) do
Puppet.override(ssl_context: ssl_context) do
client.run(client_args)
end
end
end
end
rescue Puppet::LockError
now = Time.now.to_i
wait_for_lock_deadline ||= now + Puppet[:maxwaitforlock]
if Puppet[:waitforlock] < 1
Puppet.notice _("Run of %{client_class} already in progress; skipping (%{lockfile_path} exists)") % { client_class: client_class, lockfile_path: lockfile_path }
nil
elsif now >= wait_for_lock_deadline
Puppet.notice _("Exiting now because the maxwaitforlock timeout has been exceeded.")
nil
else
Puppet.info _("Another puppet instance is already running; --waitforlock flag used, waiting for running instance to finish.")
Puppet.info _("Will try again in %{time} seconds.") % {time: Puppet[:waitforlock]}
sleep Puppet[:waitforlock]
retry
end
rescue RunTimeoutError => detail
Puppet.log_exception(detail, _("Execution of %{client_class} did not complete within %{runtimeout} seconds and was terminated.") %
{client_class: client_class, runtimeout: Puppet[:runtimeout]})
nil
rescue StandardError => detail
Puppet.log_exception(detail, _("Could not run %{client_class}: %{detail}") % { client_class: client_class, detail: detail })
nil
ensure
Puppet.runtime[:http].close
end
end
end
true
end
Puppet.notice _("Shutdown/restart in progress (%{status}); skipping run") % { status: Puppet::Application.run_status.inspect } unless block_run
result
end
def stopping?
Puppet::Application.stop_requested?
end
def run_in_fork(forking = true)
return yield unless forking or Puppet.features.windows?
atForkHandler = Puppet::Util::AtFork.get_handler
atForkHandler.prepare
begin
child_pid = Kernel.fork do
atForkHandler.child
$0 = _("puppet agent: applying configuration")
begin
exit(yield || 1)
rescue NoMemoryError
exit(254)
end
end
ensure
atForkHandler.parent
end
exit_code = Process.waitpid2(child_pid)
exit_code[1].exitstatus
end
private
# Create and yield a client instance, keeping a reference
# to it during the yield.
def with_client(transaction_uuid, job_id = nil)
begin
@client = client_class.new(transaction_uuid, job_id)
rescue StandardError => detail
Puppet.log_exception(detail, _("Could not create instance of %{client_class}: %{detail}") % { client_class: client_class, detail: detail })
return
end
yield @client
ensure
@client = nil
end
def wait_for_certificates(options)
waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert])
sm = Puppet::SSL::StateMachine.new(waitforcert: waitforcert, onetime: Puppet[:onetime])
sm.ensure_client_certificate
end
def log_disabled_message
Puppet.notice _("Skipping run of %{client_class}; administratively disabled (Reason: '%{disable_message}');\nUse 'puppet agent --enable' to re-enable.") % { client_class: client_class, disable_message: disable_message }
end
end
|