Back to index

obnam  1.1
show_plugin.py
Go to the documentation of this file.
00001 # Copyright (C) 2009, 2010  Lars Wirzenius
00002 #
00003 # This program is free software: you can redistribute it and/or modify
00004 # it under the terms of the GNU General Public License as published by
00005 # the Free Software Foundation, either version 3 of the License, or
00006 # (at your option) any later version.
00007 #
00008 # This program is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 # GNU General Public License for more details.
00012 #
00013 # You should have received a copy of the GNU General Public License
00014 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
00015 
00016 
00017 import os
00018 import re
00019 import stat
00020 import sys
00021 import time
00022 
00023 import obnamlib
00024 
00025 
00026 class ShowPlugin(obnamlib.ObnamPlugin):
00027 
00028     '''Show information about data in the backup repository.
00029     
00030     This implements commands for listing contents of root and client
00031     objects, or the contents of a backup generation.
00032     
00033     '''
00034     
00035     leftists = (2, 3, 6)
00036     min_widths = (1, 1, 1, 1, 6, 20, 1)
00037 
00038     def enable(self):
00039         self.app.add_subcommand('clients', self.clients)
00040         self.app.add_subcommand('generations', self.generations)
00041         self.app.add_subcommand('genids', self.genids)
00042         self.app.add_subcommand('ls', self.ls, arg_synopsis='[GENERATION]...')
00043         self.app.add_subcommand('nagios-last-backup-age', 
00044                                 self.nagios_last_backup_age)
00045 
00046         self.app.settings.string(['warn-age'],
00047                                  'for nagios-last-backup-age: maximum age (by '
00048                                     'default in hours) for the most recent '
00049                                     'backup before status is warning. '
00050                                     'Accepts one char unit specifier '
00051                                     '(s,m,h,d for seconds, minutes, hours, '
00052                                     'and days.', 
00053                                   metavar='AGE',
00054                                   default=obnamlib.DEFAULT_NAGIOS_WARN_AGE)
00055         self.app.settings.string(['critical-age'],
00056                                  'for nagios-last-backup-age: maximum age '
00057                                     '(by default in hours) for the most '
00058                                     'recent backup before statis is critical. '
00059                                     'Accepts one char unit specifier '
00060                                     '(s,m,h,d for seconds, minutes, hours, '
00061                                     'and days.', 
00062                                   metavar='AGE',
00063                                   default=obnamlib.DEFAULT_NAGIOS_WARN_AGE)
00064 
00065     def open_repository(self):
00066         self.app.settings.require('repository')
00067         self.app.settings.require('client-name')
00068         self.repo = self.app.open_repository()
00069         self.repo.open_client(self.app.settings['client-name'])
00070 
00071     def clients(self, args):
00072         '''List clients using the repository.'''
00073         self.open_repository()
00074         for client_name in self.repo.list_clients():
00075             print client_name
00076         self.repo.fs.close()
00077     
00078     def generations(self, args):
00079         '''List backup generations for client.'''
00080         self.open_repository()
00081         for gen in self.repo.list_generations():
00082             start, end = self.repo.get_generation_times(gen)
00083             is_checkpoint = self.repo.get_is_checkpoint(gen)
00084             if is_checkpoint:
00085                 checkpoint = ' (checkpoint)'
00086             else:
00087                 checkpoint = ''
00088             sys.stdout.write('%s\t%s .. %s (%d files, %d bytes) %s\n' %
00089                              (gen, 
00090                               self.format_time(start), 
00091                               self.format_time(end),
00092                               self.repo.client.get_generation_file_count(gen),
00093                               self.repo.client.get_generation_data(gen),
00094                               checkpoint))
00095         self.repo.fs.close()
00096 
00097     def nagios_last_backup_age(self, args):
00098         '''Check if the most recent generation is recent enough.'''
00099         self.open_repository()
00100         most_recent = None
00101 
00102         warn_age = self._convert_time(self.app.settings['warn-age'])
00103         critical_age = self._convert_time(self.app.settings['critical-age'])
00104 
00105         for gen in self.repo.list_generations():
00106             start, end = self.repo.get_generation_times(gen)
00107             if most_recent is None or start > most_recent: most_recent = start
00108         self.repo.fs.close()
00109 
00110         now = self.app.time()
00111         if (now - most_recent > critical_age):
00112             print "CRITICAL: backup is old.  last backup was %s."%(
00113                 self.format_time(most_recent))
00114             sys.exit(2)
00115         elif (now - most_recent > warn_age):
00116             print "WARNING: backup is old.  last backup was %s."%(
00117                 self.format_time(most_recent))
00118             sys.exit(2)
00119         print "OK: backup is recent.  last backup was %s."%(
00120             self.format_time(most_recent))
00121 
00122     def genids(self, args):
00123         '''List generation ids for client.'''
00124         self.open_repository()
00125         for gen in self.repo.list_generations():
00126             sys.stdout.write('%s\n' % gen)
00127         self.repo.fs.close()
00128 
00129     def ls(self, args):
00130         '''List contents of a generation.'''
00131         self.open_repository()
00132         for gen in args or [self.app.settings['generation']] or ["latest"]:
00133             gen = self.repo.genspec(gen)
00134             started, ended = self.repo.client.get_generation_times(gen)
00135             started = self.format_time(started)
00136             ended = self.format_time(ended)
00137             print 'Generation %s (%s - %s)' % (gen, started, ended)
00138             self.show_objects(gen, '/')
00139         self.repo.fs.close()
00140     
00141     def format_time(self, timestamp):
00142         return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))
00143     
00144     def isdir(self, gen, filename):
00145         metadata = self.repo.get_metadata(gen, filename)
00146         return metadata.isdir()
00147     
00148     def show_objects(self, gen, dirname):
00149         self.show_item(gen, dirname)
00150         subdirs = []
00151         for basename in sorted(self.repo.listdir(gen, dirname)):
00152             full = os.path.join(dirname, basename)
00153             if self.isdir(gen, full):
00154                 subdirs.append(full)
00155             else:
00156                 self.show_item(gen, full)
00157 
00158         for subdir in subdirs:
00159             self.show_objects(gen, subdir)
00160 
00161     def show_item(self, gen, filename):
00162         fields = self.fields(gen, filename)
00163         widths = [
00164             1, # mode
00165             5, # nlink
00166             -8, # owner
00167             -8, # group
00168             10, # size
00169             1, # mtime
00170             -1, # name
00171         ]
00172         
00173         result = []
00174         for i in range(len(fields)):
00175             if widths[i] < 0:
00176                 fmt = '%-*s'
00177             else:
00178                 fmt = '%*s'
00179             result.append(fmt % (abs(widths[i]), fields[i]))
00180         print ' '.join(result)
00181 
00182     def fields(self, gen, full):
00183         metadata = self.repo.get_metadata(gen, full)
00184 
00185         perms = ['?'] + ['-'] * 9
00186         tab = [
00187             (stat.S_IFREG, 0, '-'),
00188             (stat.S_IFDIR, 0, 'd'),
00189             (stat.S_IFLNK, 0, 'l'),
00190             (stat.S_IFIFO, 0, 'p'),
00191             (stat.S_IRUSR, 1, 'r'),
00192             (stat.S_IWUSR, 2, 'w'),
00193             (stat.S_IXUSR, 3, 'x'),
00194             (stat.S_IRGRP, 4, 'r'),
00195             (stat.S_IWGRP, 5, 'w'),
00196             (stat.S_IXGRP, 6, 'x'),
00197             (stat.S_IROTH, 7, 'r'),
00198             (stat.S_IWOTH, 8, 'w'),
00199             (stat.S_IXOTH, 9, 'x'),
00200         ]
00201         mode = metadata.st_mode or 0
00202         for bitmap, offset, char in tab:
00203             if (mode & bitmap) == bitmap:
00204                 perms[offset] = char
00205         perms = ''.join(perms)
00206         
00207         timestamp = time.strftime('%Y-%m-%d %H:%M:%S', 
00208                                   time.gmtime(metadata.st_mtime_sec))
00209 
00210         if metadata.islink():
00211             name = '%s -> %s' % (full, metadata.target)
00212         else:
00213             name = full
00214 
00215         return (perms, 
00216                  str(metadata.st_nlink or 0), 
00217                  metadata.username or '', 
00218                  metadata.groupname or '',
00219                  str(metadata.st_size or 0), 
00220                  timestamp, 
00221                  name)
00222 
00223     def format(self, fields):
00224         return ' '. join(self.align(widths[i], fields[i], i)
00225                           for i in range(len(fields)))
00226 
00227     def align(self, width, field, field_no):
00228         if field_no in self.leftists:
00229             return '%-*s' % (width, field)
00230         else:
00231             return '%*s' % (width, field)
00232 
00233     def _convert_time(self, s, default_unit='h'):
00234         m = re.match('([0-9]+)([smhdw])?$', s)
00235         if m is None: raise ValueError
00236         ticks = int(m.group(1))
00237         unit = m.group(2)
00238         if unit is None: unit = default_unit
00239 
00240         if unit == 's':
00241             None
00242         elif unit == 'm':
00243             ticks *= 60
00244         elif unit == 'h':
00245             ticks *= 60*60
00246         elif unit == 'd':
00247             ticks *= 60*60*24
00248         elif unit == 'w':
00249             ticks *= 60*60*24*7
00250         else:
00251             raise ValueError
00252         return ticks
00253