From 1190eb4d9d336fa592fc8fa7d90b977ca54fe53c Mon Sep 17 00:00:00 2001 From: htouvet Date: Tue, 30 Jan 2018 12:29:16 +0100 Subject: [PATCH] Take per section maximum_backup_age in account --- libtisbackup/common.py | 62 +++++++++++++++++++++--------------------- tisbackup.py | 40 +++++++++++++-------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/libtisbackup/common.py b/libtisbackup/common.py index 0b5ded4..e58f411 100644 --- a/libtisbackup/common.py +++ b/libtisbackup/common.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------- # This file is part of TISBackup @@ -78,7 +78,7 @@ def dateof(adatetime): # http://code.activestate.com/recipes/498181-add-thousands-separator-commas-to-formatted-number/ # Code from Michael Robellard's comment made 28 Feb 2010 # Modified for leading +, -, space on 1 Mar 2010 by Glenn Linderman -# +# # Tail recursion removed and leading garbage handled on March 12 2010, Alessandro Forghieri def splitThousands( s, tSep=',', dSep='.'): '''Splits a general float on thousands. GIGO on general input''' @@ -126,7 +126,7 @@ def check_string(test_string): def convert_bytes(bytes): if bytes is None: return None - else: + else: bytes = float(bytes) if bytes >= 1099511627776: terabytes = bytes / 1099511627776 @@ -142,7 +142,7 @@ def convert_bytes(bytes): size = '%.2fK' % kilobytes else: size = '%.2fb' % bytes - return size + return size ## {{{ http://code.activestate.com/recipes/81189/ (r2) def pp(cursor, data=None, rowlens=0, callback=None): @@ -219,7 +219,7 @@ def html_table(cur,callback=None): return "%s%s
" % (head,lines) - + def monitor_stdout(aprocess, onoutputdata,context): """Reads data from stdout and stderr from aprocess and return as a string on each chunk, call a call back onoutputdata(dataread) @@ -296,17 +296,17 @@ class BackupStat: self.db=sqlite3.connect(self.dbpath) self.initdb() else: - self.db=sqlite3.connect(self.dbpath,check_same_thread=False) + self.db=sqlite3.connect(self.dbpath,check_same_thread=False) if not "'TYPE'" in str(self.db.execute("select * from stats").description): self.updatedb() - + def updatedb(self): self.logger.debug('Update stat database') self.db.execute("alter table stats add column TYPE TEXT;") self.db.execute("update stats set TYPE='BACKUP';") self.db.commit() - + def initdb(self): assert(isinstance(self.db,sqlite3.Connection)) self.logger.debug('Initialize stat database') @@ -332,7 +332,7 @@ create index idx_stats_backup_name on stats(backup_name);""") create index idx_stats_backup_location on stats(backup_location);""") self.db.execute(""" CREATE INDEX idx_stats_backup_name_start on stats(backup_name,backup_start);""") - self.db.commit() + self.db.commit() def start(self,backup_name,server_name,TYPE,description='',backup_location=None): """ Add in stat DB a record for the newly running backup""" @@ -351,7 +351,7 @@ CREATE INDEX idx_stats_backup_name_start on stats(backup_name,backup_start);""") # update stat record self.db.execute("""\ - update stats set + update stats set total_files_count=?,written_files_count=?,total_bytes=?,written_bytes=?,log=?,status=?,backup_end=?,backup_duration=?,backup_location=? where rowid = ? @@ -377,7 +377,7 @@ CREATE INDEX idx_stats_backup_name_start on stats(backup_name,backup_start);""") backup_start=datetime2isodate() if not backup_end: backup_end=datetime2isodate() - + cur = self.db.execute("""\ insert into stats ( backup_name, @@ -408,7 +408,7 @@ CREATE INDEX idx_stats_backup_name_start on stats(backup_name,backup_start);""") status, log, backup_location, - TYPE) + TYPE) ) self.db.commit() @@ -465,7 +465,7 @@ CREATE INDEX idx_stats_backup_name_start on stats(backup_name,backup_start);""") def ssh_exec(command,ssh=None,server_name='',remote_user='',private_key='',ssh_port=22): - """execute command on server_name using the provided ssh connection + """execute command on server_name using the provided ssh connection or creates a new connection if ssh is not provided. returns (exit_code,output) @@ -520,7 +520,7 @@ class backup_generic: def __init__(self,backup_name, backup_dir,dbstat=None,dry_run=False): if not re.match('^[A-Za-z0-9_\-\.]*$',backup_name): - raise Exception('The backup name %s should contain only alphanumerical characters' % backup_name) + raise Exception('The backup name %s should contain only alphanumerical characters' % backup_name) self.backup_name = backup_name self.backup_dir = backup_dir @@ -563,7 +563,7 @@ class backup_generic: # if retention (in days) is not defined at section level, get default global one. if not self.backup_retention_time: - self.backup_retention_time = iniconf.getint('global','backup_retention_time') + self.backup_retention_time = iniconf.getint('global','backup_retention_time') # for nagios, if maximum last backup age (in hours) is not defined at section level, get default global one. if not self.maximum_backup_age: @@ -639,7 +639,7 @@ class backup_generic: def process_backup(self): """Process the backup. - launch + launch - do_preexec - do_backup - do_postexec @@ -713,10 +713,10 @@ class backup_generic: backup_location=stats['backup_location']) self.logger.error('[%s] ######### Backup finished with ERROR: %s',self.backup_name,stats['log']) - raise + raise - def checknagios(self,maxage_hours=30): + def checknagios(self): """ Returns a tuple (nagiosstatus,message) for the current backup_name Read status from dbstat database @@ -731,7 +731,7 @@ class backup_generic: self.logger.debug('[%s] checknagios : no result from query',self.backup_name) return (nagiosStateCritical,'CRITICAL : No backup found for %s in database' % self.backup_name) else: - mindate = datetime2isodate((datetime.datetime.now() - datetime.timedelta(hours=maxage_hours))) + mindate = datetime2isodate((datetime.datetime.now() - datetime.timedelta(hours=self.maximum_backup_age))) self.logger.debug('[%s] checknagios : looking for most recent OK not older than %s',self.backup_name,mindate) for b in q: if b['backup_end'] >= mindate and b['status'] == 'OK': @@ -784,15 +784,15 @@ class backup_generic: returncode = process.returncode if (returncode != 0): self.logger.error("[" + self.backup_name + "] shell program exited with error code: %s"%log) - raise Exception("[" + self.backup_name + "] shell program exited with error code " + str(returncode), cmd) + raise Exception("[" + self.backup_name + "] shell program exited with error code " + str(returncode), cmd) else: - self.logger.info("[" + self.backup_name + "] deleting snapshot volume: %s"%oldbackup_location.encode('ascii')) + self.logger.info("[" + self.backup_name + "] deleting snapshot volume: %s"%oldbackup_location.encode('ascii')) else: shutil.rmtree(oldbackup_location.encode('ascii')) if os.path.isfile(oldbackup_location) and self.backup_dir in oldbackup_location : self.logger.debug('[%s] removing file "%s"',self.backup_name,oldbackup_location) if not self.dry_run: - os.remove(oldbackup_location) + os.remove(oldbackup_location) self.logger.debug('Cleanup_backup : Removing records from DB : [%s]-"%s"',self.backup_name,oldbackup_location) if not self.dry_run: self.dbstat.db.execute('update stats set TYPE="CLEAN" where backup_name=? and backup_location=?',(self.backup_name,oldbackup_location)) @@ -812,7 +812,7 @@ class backup_generic: """scan existing backups and insert stats in database""" registered = [b['backup_location'] for b in self.dbstat.query('select distinct backup_location from stats where backup_name=?',self.backup_name)] raise Exception('Abstract method') - + def export_latestbackup(self,destdir): """Copy (rsync) latest OK backup to external storage located at locally mounted "destdir" """ @@ -828,11 +828,11 @@ class backup_generic: raise Exception('No database') else: latest_sql = """\ - select status, backup_start, backup_end, log, backup_location, total_bytes - from stats + select status, backup_start, backup_end, log, backup_location, total_bytes + from stats where backup_name=? and status='OK' and TYPE='BACKUP' order by backup_start desc limit 30""" - self.logger.debug('[%s] export_latestbackup : sql query "%s" %s',self.backup_name,latest_sql,self.backup_name) + self.logger.debug('[%s] export_latestbackup : sql query "%s" %s',self.backup_name,latest_sql,self.backup_name) q = self.dbstat.query(latest_sql,(self.backup_name,)) if not q: self.logger.debug('[%s] export_latestbackup : no result from query',self.backup_name) @@ -849,10 +849,10 @@ class backup_generic: backup_source += '/' if backup_dest[-1] <> '/': backup_dest += '/' - + if not os.path.isdir(backup_dest): os.makedirs(backup_dest) - + options = ['-aP','--stats','--delete-excluded','--numeric-ids','--delete-after'] if self.logger.level: options.append('-P') @@ -901,7 +901,7 @@ class backup_generic: stats['status']='OK' self.logger.info('export backup from %s to %s OK, %d bytes written for %d changed files' % (backup_source,backup_dest,stats['written_bytes'],stats['written_files_count'])) - + endtime = time.time() duration = (endtime-starttime)/3600.0 if not self.dry_run and self.dbstat: @@ -914,7 +914,7 @@ class backup_generic: written_bytes=stats['written_bytes'], status=stats['status'], log=stats['log'], - backup_location=backup_dest) + backup_location=backup_dest) return stats @@ -925,4 +925,4 @@ if __name__ == '__main__': handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler) - dbstat = BackupStat('/backup/data/log/tisbackup.sqlite') + dbstat = BackupStat('/backup/data/log/tisbackup.sqlite') diff --git a/tisbackup.py b/tisbackup.py index 64e83f1..5afbc9e 100755 --- a/tisbackup.py +++ b/tisbackup.py @@ -50,14 +50,14 @@ usage="""\ TIS Files Backup system. -action is either : +action is either : backup : launch all backups or a specific one if -s option is used cleanup : removed backups older than retension period checknagios : check all or a specific backup against max_backup_age parameter dumpstat : dump the content of database for the last 20 backups retryfailed : try to relaunch the last failed backups listdrivers : list available backup types and parameters for config inifile - exportbackup : copy lastest OK backups from local to location defned by --exportdir parameter + exportbackup : copy lastest OK backups from local to location defned by --exportdir parameter register_existing : scan backup directories and add missing backups to database""" version="VERSION" @@ -115,7 +115,7 @@ class tis_backup: # TODO limit backup to one backup on the command line - def checknagios(self,sections=[],maxage_hours=None): + def checknagios(self,sections=[]): try: if not sections: sections = [backup_item.backup_name for backup_item in self.backup_list] @@ -133,7 +133,7 @@ class tis_backup: assert(isinstance(backup_item,backup_generic)) if not maxage_hours: maxage_hours = backup_item.maximum_backup_age - (nagiosstatus,log) = backup_item.checknagios(maxage_hours=maxage_hours) + (nagiosstatus,log) = backup_item.checknagios() if nagiosstatus == nagiosStateCritical: critical.append((backup_item.backup_name,log)) elif nagiosstatus == nagiosStateWarning : @@ -195,7 +195,7 @@ class tis_backup: errors = [] if not sections: sections = [backup_item.backup_name for backup_item in self.backup_list] - + self.logger.info('Processing backup for %s' % (','.join(sections)) ) for backup_item in self.backup_list: if not sections or backup_item.backup_name in sections: @@ -220,9 +220,9 @@ class tis_backup: errors = [] if not sections: sections = [backup_item.backup_name for backup_item in self.backup_list] - + self.logger.info('Exporting OK backups for %s to %s' % (','.join(sections),exportdir) ) - + for backup_item in self.backup_list: if backup_item.backup_name in sections: try: @@ -249,7 +249,7 @@ class tis_backup: mindate = datetime2isodate((datetime.datetime.now() - datetime.timedelta(hours=maxage_hours))) failed_backups = self.dbstat.query("""\ select distinct backup_name as bname - from stats + from stats where status="OK" and backup_start>=?""",(mindate,)) defined_backups = map(lambda f:f.backup_name, [ x for x in self.backup_list if not isinstance(x, backup_null) ]) @@ -284,7 +284,7 @@ class tis_backup: processed = False if not sections: sections = [backup_item.backup_name for backup_item in self.backup_list] - + self.logger.info('Processing cleanup for %s' % (','.join(sections)) ) for backup_item in self.backup_list: if backup_item.backup_name in sections: @@ -301,13 +301,13 @@ class tis_backup: def register_existingbackups(self,sections = []): if not sections: sections = [backup_item.backup_name for backup_item in self.backup_list] - + self.logger.info('Append existing backups to database...') for backup_item in self.backup_list: if backup_item.backup_name in sections: backup_item.register_existingbackups() - def html_report(self): + def html_report(self): for backup_item in self.backup_list: if not section or section == backup_item.backup_name: assert(isinstance(backup_item,backup_generic)) @@ -321,9 +321,9 @@ class tis_backup: worst_nagiosstatus = nagiosstatus -def main(): +def main(): (options,args)=parser.parse_args() - + if len(args) != 1: print "ERROR : You must provide one action to perform" parser.print_usage() @@ -341,14 +341,14 @@ def main(): config_file =options.config dry_run = options.dry_run verbose = options.verbose - + loglevel = options.loglevel # setup Logger logger = logging.getLogger('tisbackup') - hdlr = logging.StreamHandler() + hdlr = logging.StreamHandler() hdlr.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) - logger.addHandler(hdlr) + logger.addHandler(hdlr) # set loglevel if loglevel in ('debug','warning','info','error','critical'): @@ -374,14 +374,14 @@ def main(): if action!='checknagios': hdlr = logging.FileHandler(os.path.join(log_dir,'tisbackup_%s.log' % (backup_start_date))) hdlr.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) - logger.addHandler(hdlr) + logger.addHandler(hdlr) # Main backup = tis_backup(dry_run=dry_run,verbose=verbose,backup_base_dir=backup_base_dir) backup.read_ini_file(config_file) backup_sections = options.sections.split(',') if options.sections else [] - + all_sections = [backup_item.backup_name for backup_item in backup.backup_list] if not backup_sections: backup_sections = all_sections @@ -410,8 +410,8 @@ def main(): backup.retry_failed_backups() elif action == "register_existing": backup.register_existingbackups(backup_sections) - - + + else: logger.error('Unhandled action "%s", quitting...',action) sys.exit(1)