1#!/bin/sh
2#
3# Local puppet daemon activity checker.
4#
5# $Id: check_puppet 382 2012-07-27 15:36:07Z alexey $
6
7# Puppet client and Puppet master daemon pidfiles.
8: ${PUPPETD_PIDFILE:="/var/run/puppet/agent.pid"}
9: ${PUPPET_MASTERD_PIDFILE:="/var/run/puppet/master.pid"}
10
11# Where puppetd and puppetmasterd store state associated
12# with the running configuration.
13: ${PUPPETD_STATE:="/var/puppet/state/state.yaml"}
14
15# Timeouts for state mtime in seconds.
16: ${PUPPETD_STATE_WARNING:=3600}
17: ${PUPPETD_STATE_CRITICAL:=36000}
18
19# The default checks triggers. The most popular puppet installation is
20# client-only instance. So puppet master checks disabled by default.
21: ${CHECK_PUPPETD:="yes"}
22: ${CHECK_PUPPET_MASTERD:="no"}
23
24# Prefix output string with one of the following keywords:
25# OK, WARNING, CRITICAL, UNKNOWN.
26: ${OUTPUT_PREFIXED:="no"}
27
28# Configurable options ends here.
29
30# The option string. See getopt(3) for details.
31OPTIONS="hvcCmMp:P:s:e:E:"
32
33# Exit codes.
34STATE_OK=0
35STATE_WARNING=1
36STATE_CRITICAL=2
37STATE_UNKNOWN=3
38STATE_DEPENDENT=4
39
40# usage() and version() are helper functions to work with help2man program.
41#
42# Outputs program usage instructions.
43usage()
44{
45	_me=$(basename $0)
46	cat << EOF
47${_me} observes for Puppet daemon activity by checking the pid file against the
48process running on the system for both Puppet master daemon and Puppet client.
49In the client mode local configuration freshness is also being checked.
50
51Usage: ${_me} [-h | -v] [OPTIONS]
52
53Options:
54   -h                Print this help and then exit immediately.
55   -v                Print version and exit.
56   -c                Enable puppet client checking mode. (default)
57   -C                Do not check puppetd.
58   -m                Enable puppet master daemon checking mode.
59   -M                Do not check puppet master daemon. (default)
60   -p path-to-pid    Running puppet client pid file. You should ensure that
61		     user running this plugin have sufficient permissions to
62		     access this file. Default is /var/run/puppet/agent.pid
63   -P path-to-pid    Puppet master daemon pid file. Default pid file path is
64                     /var/run/puppet/master.pid
65   -s path-to-yaml   Puppet client local configuration YAML. By default puppet
66                     stores it's state in /var/puppet/state/state.yaml
67   -e                Configuration expiration warning delay.
68   -E                Configuration expiration critical delay.
69
70Report bugs to <alexey@renatasystems.org>
71EOF
72	# Return exitcode given as $1 argument.
73	[ $# -eq 1 ] && exit $1
74
75	# Or exit with OK state.
76	exit 0
77}
78
79# Output version and exit.
80version()
81{
82	cat <<EOF
83$(basename $0) 1.3
84
85Written by Alexey V. Degtyarev
86EOF
87	exit 0
88}
89
90# Helper function to determine given argument either `yes' or `no'.
91check_yesno()
92{
93	# Accepts exaclty one argument.
94	[ $# -eq 1 ] || return 2
95
96	case $1 in
97		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0;;
98		[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1;;
99		*) return 2;
100	esac
101
102	# Should not be here ever.
103	return 0
104}
105
106# Parse given options with builtin getopts function.
107parse_options()
108{
109	while getopts ${OPTIONS} option $@; do
110		case ${option} in
111			h) usage ${STATE_UNKNOWN};;
112			v) version;;
113			c) CHECK_PUPPETD="yes";;
114			C) CHECK_PUPPETD="no";;
115			m) CHECK_PUPPET_MASTERD="yes";;
116			M) CHECK_PUPPET_MASTERD="no";;
117			p) PUPPETD_PIDFILE=${OPTARG};;
118			P) PUPPET_MASTERD_PIDFILE=${OPTARG};;
119			s) PUPPETD_STATE=${OPTARG};;
120			e) PUPPETD_STATE_WARNING=${OPTARG};;
121			E) PUPPETD_STATE_CRITICAL=${OPTARG};;
122			?) echo; usage ${STATE_UNKNOWN};;
123		esac
124	done
125
126	return 0
127}
128
129
130check_options()
131{
132	# Options -C and -M should not be given together.
133	( check_yesno ${CHECK_PUPPETD} || \
134	check_yesno ${CHECK_PUPPET_MASTERD} ) || \
135	{ echo "ERROR: Neither -c nor -m option specified. Nothing to do."; \
136		return 1; }
137
138	# Check that levels are not conflicts each others.
139	[ ${PUPPETD_STATE_WARNING} -gt ${PUPPETD_STATE_CRITICAL} ] && \
140	{ echo "ERROR: WARNING level must not exceed CRITICAL one."; \
141		return 1; }
142
143	return 0
144}
145
146# Checks that:
147# 1. pid file given in $1 exists;
148# 2. pid number specified in pidfile is a real process identificator, i.e.
149# process with such number is really exists;
150# 3. this process is running puppetd or puppet masterd application with ruby
151# interpreter;
152#
153# Return codes:
154# 0: puppetd found running (and set variable name in $2 to pid number);
155# 1: Empty pid file name given;
156# 2: Can't read the pidfile (i.e. no such file or no access);
157# 3: Can't find puppet process running with pid from ${PUPPETD_PIDFILE};
158# 4: Process running with pid ${PUPPETD_PIDFILE} is not a puppet daemon;
159check_process()
160{
161	# Check the pid file existence.
162	[ ! -z $1 ] || return 1
163	[ ! -f $1 -o ! -r $1 ] && return 2
164
165	# Take the process name and arguments from process tree.
166	# Linux users need to use some other command.
167	_procname="`pgrep -F $1 -l -f`"
168	if [ $? -ne 0 ]; then
169		return 3
170	fi
171
172	_variable=$2
173
174# 14659 /usr/local/bin/ruby18 /usr/local/bin/puppetd --rundir /var/run/puppet
175	set -- ${_procname}
176
177	# Take the basename of interpreter running puppetd instance we got from
178	# process list.
179	_running_interpreter=${2##*/}
180
181	# Preserve the pid
182	_pid=$1
183
184	# And take the interpreter from puppetd executable.
185	read _interpreter < $3
186	case "${_interpreter}" in
187		# strip #!
188		\#!*) _interpreter=${_interpreter#\#!}
189		set -- ${_interpreter}
190		_interpreter=${_interpreter##*/}
191		;;
192		*) _interpreter="/nonexistent"
193		;;
194	esac
195
196	# Compare two interpretators from ps and from puppetd executable.
197	if [ ${_running_interpreter} != ${_interpreter} ]; then
198		return 4
199	fi
200
201	eval ${_variable}=${_pid}
202
203	return 0
204}
205
206# Checks:
207# 1. state file ${PUPPETD_STATE} existence.
208# 2. state file mtime against ${PUPPETD_STATE_WARNING} and
209# ${PUPPETD_STATE_CRITICAL}
210#
211# Returns:
212# 0: State file OK.
213# 1: Empty name for state file given.
214# 2: State file was not found (or no access).
215# 3: State file's mtime is older than the WARNING point, but is newer than the
216# CRITICAL one.
217# 4: State file's mtime is older than CRITICAL timepoint.
218check_activity()
219{
220	# Check the state file existence.
221	[ ! -z ${PUPPETD_STATE} ] || return 1
222	[ ! -r ${PUPPETD_STATE} ] && return 2
223
224	# Take file states.
225	eval `stat -s ${PUPPETD_STATE}`
226
227	local _now=`date +%s`
228	local _diff=`echo ${_now} - ${st_mtime} |bc`
229	PUPPETD_STATE_MTIME=${st_mtime}
230
231	# State mtime is less than PUPPETD_STATE_WARNING?
232	if [ ${_diff} -lt ${PUPPETD_STATE_WARNING} ]; then
233		return 0
234	# State is WARN < state < CRIT ?
235	elif [ ${_diff} -gt ${PUPPETD_STATE_WARNING} -a \
236		${_diff} -lt ${PUPPETD_STATE_CRITICAL} ]; then
237		return 3
238	# Critical state.
239	elif [ ${_diff} -gt ${PUPPETD_STATE_CRITICAL} ]; then
240		return 4
241	fi
242
243	return 0
244}
245
246# Sets global state information. If the given state code number is less than
247# existing one (i.e. already set before) - ignore new code silently. This make
248# possible to get the worst code error seen while script goes through several
249# check points.
250set_state()
251{
252	[ $# -ne 1 ] && return 1
253
254	[ $1 -lt ${STATE} ] || STATE=$1
255
256	return 0
257}
258
259# Checks puppet daemon process status and process the exit code returned.
260# The ${OUTPUT_STR} status variable will contain some status information.
261check_puppetd()
262{
263	check_process ${PUPPETD_PIDFILE} PUPPETD_PID
264	case $? in
265		# Pid file and process seems to be OK.
266		0)
267		_str="puppet client is running as pid: ${PUPPETD_PID}"
268		set_state ${STATE_OK};;
269
270		# Exit code 1 means unreachable pid file.
271		1)
272		_str="cannot found puppetd pid: UNKNOWN"
273		set_state ${STATE_UNKNOWN};;
274
275		# Codes 2 and 3 telling us puppet daemon is down.
276		2|3)
277		_str="puppet client is not running: CRITICAL"
278		set_state ${STATE_CRITICAL};;
279
280		# Should never been reached.
281		*)
282		_str="plugin error: UNKNOWN"
283		set_state ${STATE_UNKNOWN};;
284	esac
285
286	# Concat nonempty output or set the output string.
287	[ -z "${OUTPUT_STR}" ] && OUTPUT_STR=${_str} || \
288		OUTPUT_STR="${OUTPUT_STR}, ${_str}"
289
290	return 0
291}
292
293# Checks puppetmaster daemon process status and process the exit code returned.
294# The ${OUTPUT_STR} status variable will contain some status information.
295check_puppet_masterd()
296{
297	check_process ${PUPPET_MASTERD_PIDFILE} PUPPET_MASTERD_PID
298	case $? in
299		# Pid file and process seems to be OK.
300		0)
301		_str="puppet master is running as pid: ${PUPPET_MASTERD_PID}"
302		set_state ${STATE_OK};;
303
304		# Exit code 1 means unreachable pid file.
305		1)
306		_str="cannot found puppet master pid: UNKNOWN"
307		set_state ${STATE_UNKNOWN};;
308
309		# Codes 2 and 3 telling us puppet daemon is down.
310		2|3)
311		_str="puppet master is not running: CRITICAL"
312		set_state ${STATE_CRITICAL};;
313
314		# Should never been reached.
315		*)
316		_str="plugin error: UNKNOWN"
317		set_state ${STATE_UNKNOWN};;
318	esac
319
320	# Concat nonempty output or set the output string.
321	[ -z "${OUTPUT_STR}" ] && OUTPUT_STR=${_str} || \
322		OUTPUT_STR="${OUTPUT_STR}, ${_str}"
323
324	return 0
325}
326
327# The main routine.
328#
329# Checks process status and configuration file state and outputs status string
330# exiting with corresponding status code.
331main()
332{
333	# Set the default values.
334	STATE=${STATE_OK}
335	PUPPETD_PID=-1
336
337	# Check the client daemon.
338	check_yesno ${CHECK_PUPPETD} && check_puppetd
339
340	# Check the master daemon.
341	check_yesno ${CHECK_PUPPET_MASTERD} && check_puppet_masterd
342
343	# This will check the configuration file freshness and set the
344	# appropriate status.
345	check_yesno ${CHECK_PUPPETD} &&
346	{ check_activity
347	case $? in
348		# Configuration freshness OK.
349		0)
350		_time=`date -j -f %s ${PUPPETD_STATE_MTIME} +"%H:%M, %d %b %Y"`
351		_str="configuration last recieved at ${_time}"
352		set_state ${STATE_OK};;
353
354		# Empty name for state file given?
355		1)
356		_str="cannot found state file: UNKNOWN"
357		set_state ${STATE_UNKNOWN};;
358
359		# Unreachable configuration filename.
360		2)
361		_str="YAML state was not yet recieved: UNKNOWN"
362		set_state ${STATE_UNKNOWN};;
363
364		# Configuration expiring in WARNING state.
365		3)
366		_now=`date +%s`;
367		_time=`echo ${_now}-${PUPPETD_STATE_MTIME} |bc`
368		_str="config overdue in ${_time} seconds: WARNING"
369		set_state ${STATE_WARNING};;
370
371		# Configuration too old.
372		4)
373		_now=`date +%s`;
374		_time=`date -j -f %s ${PUPPETD_STATE_MTIME} +"%H:%M, %d %b %Y"`
375		_str="configuration expired at ${_time}: CRITICAL"
376		set_state ${STATE_CRITICAL};;
377	esac
378
379	# Concat nonempty output or set the output string.
380	[ -z "${OUTPUT_STR}" ] && OUTPUT_STR=${_str} || \
381		OUTPUT_STR="${OUTPUT_STR}, ${_str}"
382	}
383
384	# Select preferable status prefix.
385	if check_yesno ${OUTPUT_PREFIXED}; then
386		case ${STATE} in
387			${STATE_OK}) _status_str="OK:";;
388			${STATE_WARNING}) _status_str="WARNING:";;
389			${STATE_CRITICAL}) _status_str="CRITICAL:";;
390			${STATE_UNKNOWN}) _status_str="UNKNOWN:";;
391			*) _status_str="ERROR:";;
392		esac
393		_status_str="${_status_str} ${OUTPUT_STR}."
394	else
395		_status_str="${OUTPUT_STR}."
396	fi
397
398	# Output status, set exit code and exit.
399	echo ${_status_str}
400	return ${STATE}
401}
402
403# Parse and check command line options.
404parse_options $* && check_options || return ${STATE_UNKNOWN}
405
406main
407
408exit $?
409