File: daemon.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (182 lines) | stat: -rw-r--r-- 4,781 bytes parent folder | download
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
177
178
179
180
181
182
require_relative '../puppet/application'
require_relative '../puppet/scheduler'

# Run periodic actions in a daemonized process.
#
# A Daemon has 2 parts:
#   * config reparse
#   * an agent that responds to #run
#
# The config reparse will occur periodically based on Settings. The agent
# is run periodically and a time interval based on Settings. The config
# reparse will update this time interval when needed.
#
# The Daemon is also responsible for signal handling, starting, stopping,
# running the agent on demand, and reloading the entire process. It ensures
# that only one Daemon is running by using a lockfile.
#
# @api private
class Puppet::Daemon
  SIGNAL_CHECK_INTERVAL = 5

  attr_accessor :argv
  attr_reader :signals, :agent

  def initialize(agent, pidfile, scheduler = Puppet::Scheduler::Scheduler.new())
    raise Puppet::DevError, _("Daemons must have an agent") unless agent
    @scheduler = scheduler
    @pidfile = pidfile
    @agent = agent
    @signals = []
  end

  def daemonname
    Puppet.run_mode.name
  end

  # Put the daemon into the background.
  def daemonize
    pid = fork
    if pid
      Process.detach(pid)
      exit(0)
    end

    create_pidfile

    # Get rid of console logging
    Puppet::Util::Log.close(:console)

    Process.setsid
    Dir.chdir("/")

    close_streams
  end

  # Close stdin/stdout/stderr so that we can finish our transition into 'daemon' mode.
  # @return nil
  def self.close_streams()
    Puppet.debug("Closing streams for daemon mode")
    begin
      $stdin.reopen "/dev/null"
      $stdout.reopen "/dev/null", "a"
      $stderr.reopen $stdout
      Puppet::Util::Log.reopen
      Puppet.debug("Finished closing streams for daemon mode")
    rescue => detail
      Puppet.err "Could not start #{Puppet.run_mode.name}: #{detail}"
      Puppet::Util::replace_file("/tmp/daemonout", 0644) do |f|
        f.puts "Could not start #{Puppet.run_mode.name}: #{detail}"
      end
      exit(12)
    end
  end

  # Convenience signature for calling Puppet::Daemon.close_streams
  def close_streams()
    Puppet::Daemon.close_streams
  end

  def reexec
    raise Puppet::DevError, _("Cannot reexec unless ARGV arguments are set") unless argv
    command = $0 + " " + argv.join(" ")
    Puppet.notice "Restarting with '#{command}'"
    stop(:exit => false)
    exec(command)
  end

  def reload
    agent.run({:splay => false})
  rescue Puppet::LockError
    Puppet.notice "Not triggering already-running agent"
  end

  def restart
    Puppet::Application.restart!
    reexec
  end

  def reopen_logs
    Puppet::Util::Log.reopen
  end

  # Trap a couple of the main signals.  This should probably be handled
  # in a way that anyone else can register callbacks for traps, but, eh.
  def set_signal_traps
    [:INT, :TERM].each do |signal|
      Signal.trap(signal) do
        Puppet.notice "Caught #{signal}; exiting"
        stop
      end
    end

    # extended signals not supported under windows
    if !Puppet::Util::Platform.windows?
      signals = {:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs }
      signals.each do |signal, method|
        Signal.trap(signal) do
          Puppet.notice "Caught #{signal}; storing #{method}"
          @signals << method
        end
      end
    end
  end

  # Stop everything
  def stop(args = {:exit => true})
    Puppet::Application.stop!

    remove_pidfile

    Puppet::Util::Log.close_all

    exit if args[:exit]
  end

  def start
    create_pidfile
    run_event_loop
  end

  private

  # Create a pidfile for our daemon, so we can be stopped and others
  # don't try to start.
  def create_pidfile
    raise "Could not create PID file: #{@pidfile.file_path}" unless @pidfile.lock
  end

  # Remove the pid file for our daemon.
  def remove_pidfile
    @pidfile.unlock
  end

  # Loop forever running events - or, at least, until we exit.
  def run_event_loop
    agent_run = Puppet::Scheduler.create_job(Puppet[:runinterval], Puppet[:splay], Puppet[:splaylimit]) do
      # Splay for the daemon is handled in the scheduler
      agent.run(:splay => false)
    end

    reparse_run = Puppet::Scheduler.create_job(Puppet[:filetimeout]) do
      Puppet.settings.reparse_config_files
      agent_run.run_interval = Puppet[:runinterval]
      if Puppet[:filetimeout] == 0
        reparse_run.disable
      else
        reparse_run.run_interval = Puppet[:filetimeout]
      end
    end

    signal_loop = Puppet::Scheduler.create_job(SIGNAL_CHECK_INTERVAL) do
      while method = @signals.shift #rubocop:disable Lint/AssignmentInCondition
        Puppet.notice "Processing #{method}"
        send(method)
      end
    end

    reparse_run.disable if Puppet[:filetimeout] == 0

    @scheduler.run_loop([reparse_run, agent_run, signal_loop])
  end
end