3 # queries a bacula database for the last backup of a given host
5 # Copyright 2010, 2011, 2013 Peter Palfrader
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 import psycopg2.extras
39 def convert_time(s, default_unit='h'):
40 m = re.match('([0-9]+)([smhdw])?$', s)
41 if m is None: raise ValueError
42 ticks = int(m.group(1))
44 if unit is None: unit = default_unit
47 elif unit == 'm': ticks *= 60
48 elif unit == 'h': ticks *= 60*60
49 elif unit == 'd': ticks *= 60*60*24
50 elif unit == 'w': ticks *= 60*60*24*7
51 else: raise ValueError
55 parser = optparse.OptionParser()
56 parser.set_usage("%prog [options] <host> [<backup-level>]")
57 parser.add_option("-w", "--warn", metavar="AGE", dest="warn",
58 help="Warn if backup older than (default: 28h)")
59 parser.add_option("-c", "--critical", metavar="AGE", dest="critical",
60 help="Warn if backup older than (default: 72h)")
61 parser.add_option("-d", "--db-connect-string", metavar="connect-string", dest="db",
62 help="Database connect string")
63 parser.add_option("-D", "--db-connect-string-file", metavar="FILE", dest="dbfile",
64 default='/etc/nagios/bacula-database',
65 help="File to read database connect string from (/etc/nagios/bacula-database)")
66 (options, args) = parser.parse_args()
76 sys.exit(codes['UNKNOWN'])
78 if options.warn is None: options.warn = '28'
79 if options.critical is None: options.critical = '72'
80 options.warn = convert_time(options.warn)
81 options.critical = convert_time(options.critical)
83 if options.db is not None:
85 elif options.dbfile is not None:
86 options.db = open(options.dbfile).read().rstrip()
88 print >>sys.stderr, "Need one of -d or -D."
89 sys.exit(codes['UNKNOWN'])
92 query = "SELECT min(extract('epoch' from (CURRENT_TIMESTAMP - realendtime))) AS age FROM job WHERE name=%(host)s AND jobstatus='T'"
93 params = { 'host': host }
95 query += " AND level=%(level)s"
96 params['level'] = level
100 conn = psycopg2.connect(options.db)
102 cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
103 cursor.execute(query, params)
104 records = cursor.fetchall()
105 if len(records) == 0 or records[0][0] is None:
106 print "CRITICAL: No backups of %s/%s."%(host, level)
107 sys.exit(codes['CRITICAL'])
108 elif len(records) > 1:
109 print "UNKNOWN: got too many records back from query."
110 sys.exit(codes['UNKNOWN'])
111 elif records[0]['age'] > options.critical:
112 print "CRITICAL: Last backup of %s/%s is %.2f days old."%(host, level, float(records[0]['age'])/3600/24)
113 sys.exit(codes['CRITICAL'])
114 elif records[0]['age'] > options.warn:
115 print "WARN: Last backup of %s/%s is %.2f days old."%(host, level, float(records[0]['age'])/3600/24)
116 sys.exit(codes['WARN'])
118 print "OK: Last backup of %s/%s is %.2f days old."%(host, level, float(records[0]['age'])/3600/24)
119 sys.exit(codes['OK'])