--- /dev/null
+#!/usr/bin/env python
+#
+# check_bacula_client Nagios plugin to check Bacula client backups
+# Copyright (C) 2010 Tom Payne
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from datetime import datetime, timedelta
+from optparse import OptionParser, OptionValueError
+import re
+import sys
+import time
+
+import pexpect
+
+
+OK, WARNING, CRITICAL, UNKNOWN = xrange(0, 4)
+status_message = 'OK WARNING CRITICAL UNKNOWN'.split()
+
+MULTIPLIERS = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400, 'w': 604800}
+DIVISORS = ((60, 'minutes'), (60, 'hours'), (24, 'days'), (7, 'weeks'))
+
+
+def parse_period(option, opt_str, value, parser):
+ m = re.match(r'(\d+(?:\.\d+)?)(%s)\Z' % '|'.join(MULTIPLIERS.keys()), value)
+ if not m:
+ raise OptionValueError('invalid period - %s' % value)
+ setattr(parser.values, option.dest, timedelta(seconds=float(m.group(1)) * MULTIPLIERS[m.group(2)]))
+
+
+def main(argv):
+ parser = OptionParser()
+ parser.add_option('-H', metavar='ADDRESS', dest='host', help='client name')
+ parser.add_option('-w', metavar='PERIOD', type=str, dest='warning', action='callback', callback=parse_period, help='generate warning if last successful backup older than PERIOD')
+ parser.add_option('-c', metavar='PERIOD', type=str, dest='critical', action='callback', callback=parse_period, help='generate critical if last successful backup older than PERIOD')
+ parser.add_option('-b', metavar='PATH', dest='bconsole', help='path to bconsole')
+ parser.set_defaults(bconsole='/usr/bin/bconsole')
+ options, args = parser.parse_args(argv[1:])
+ exit_status, message = OK, None
+ child = pexpect.spawn(options.bconsole, ['-n'])
+ try:
+ child.expect(r'\n\*')
+ child.sendline('status client=%s.debian.org-fd' % options.host)
+ if child.expect_list([re.compile(r'Terminated Jobs:'), re.compile(r'Error: Client resource .* does not exist.')]):
+ raise RuntimeError('unknown client %s' % options.host)
+ child.expect(r'\n\*')
+ r = re.compile(r'\s*(\d+)\s+(\S+)\s+(\S+)\s+(\d+\.\d+\s+[KMGTP]|0)\s+OK\s+(\S+\s+\S+)')
+ job_id = level = files = bytes = finished = None
+ for line in child.before.splitlines():
+ m = r.match(line)
+ if m:
+ job_id = int(m.group(1))
+ level = m.group(2)
+ files = int(re.sub(r',', '', m.group(3)))
+ bytes = re.sub(r'\s+', '', m.group(4))
+ finished = datetime(*(time.strptime(m.group(5), '%d-%b-%y %H:%M')[0:6]))
+ if job_id is None:
+ raise RuntimeError('no terminated jobs')
+ age = datetime.now() - finished
+ if options.warning and age > options.warning:
+ exit_status = WARNING
+ if options.critical and age > options.critical:
+ exit_status = CRITICAL
+ age, units = 24.0 * 60 * 60 * age.days + age.seconds, 'seconds'
+ for d, u in DIVISORS:
+ if age < d:
+ break
+ else:
+ age /= d
+ units = u
+ message = '%s, %d files, %sB, %s (%.1f %s ago)' % (level, files, bytes, finished, age, units)
+ except RuntimeError:
+ exit_status, message = (CRITICAL, str(sys.exc_info()[1]))
+ child.sendeof()
+ child.expect(pexpect.EOF)
+ print '%s: %s' % (status_message[exit_status], message)
+ sys.exit(exit_status)
+
+
+if __name__ == '__main__':
+ main(sys.argv)