Advertisement
Guest User

Untitled

a guest
Jun 13th, 2017
128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.79 KB | None | 0 0
  1. import backup_prop, logging.handlers, os, subprocess, shutil, sys, re, ftplib, tarfile
  2. from smtplib import SMTP_SSL as SMTP
  3. from email.mime.text import MIMEText
  4. from subprocess import CalledProcessError
  5. from datetime import datetime
  6. from argparse import ArgumentParser
  7. from ftplib import FTP
  8.  
  9. ##############  Defenitions  #####################
  10.  
  11. LOG_DIR = 'backup_logs'
  12. BACKUP_OPT_DIR = 'backup_utils'
  13. BACKUP_OPT_FILE = 'backup_utils/backup_opt'
  14. TAR_BACKUP_META = 'backup_utils/backup_meta.snar'
  15. KP_TAR_BACKUP_META = 'backup_utils/kp_backup_meta.snar'
  16. BACKUP_INPROGRESS = '.in_progress'
  17.  
  18. DATE_PATTERN = '%Y-%m-%d_%H-%M'
  19.  
  20. FTP_SIZES_REG_PATTERN = '.* (\d*) Kbytes used .* (\d*) Kb'
  21.  
  22. class BackupFailedException(Exception):pass
  23.  
  24. emailSent = False;
  25. def sendMail(msg_body="Backup has failed!"):
  26.  
  27.     msg = MIMEText(msg_body)
  28.  
  29.     msg['Subject'] = 'Cassiopeia Central server backup has failed!'
  30.     msg['From'] = backup_prop.from_addr
  31.    
  32.     smtp = SMTP()
  33.     smtp.set_debuglevel(True)
  34.     smtp.connect(backup_prop.smtp_server)
  35.     if backup_prop.is_smtp_authorization:
  36.         smtp.login(backup_prop.smtp_login, backup_prop.smtp_pass)
  37.     smtp.sendmail(backup_prop.from_addr, backup_prop.smtp_send_to, msg.as_string())
  38.     smtp.close()
  39.     emailSent = True;
  40.    
  41. def removeFiles(fileList):
  42.     for f in fileList:
  43.         if os.path.isfile(f):
  44.             try:
  45.                 logger.debug('Removing file: %s'%f)
  46.                 os.remove(f)
  47.             except:
  48.                 logger.error('Error while removing file %s'%f)
  49.  
  50. def removeOldBackup(ftp):
  51.     logger.debug('Removing old backup')
  52.     ftp.cwd('/backups')
  53.     try:
  54.         files = ftp.nlst()
  55.         files.sort()
  56.     except ftplib.error_perm, resp:
  57.         if str(resp) == "550 No files found":
  58.             print "no files in this directory"
  59.         else:
  60.             raise
  61.     logger.debug('Backup %s will be removed', files[0])
  62.     removeDir(ftp, files[0])
  63.    
  64. def removeDir(ftp, dirName):
  65.     ftp.cwd(dirName)
  66.    
  67.     lines = []  # each level has own lines
  68.     ftp.dir(lines.append)  # list current remote dir
  69.    
  70.     for line in lines:
  71.         parsed = line.split()  # split on whitespace
  72.         permiss = parsed[0]  # assume 'drw... ... filename'
  73.         fname = parsed[-1]
  74.         if fname in ('.', '..'):  # some include cwd and parent
  75.             continue
  76.         elif permiss[0] != 'd':  # simple file: delete
  77.             print('file', fname)
  78.             ftp.delete(fname)
  79.         else:  # directory: recur, del
  80.             print('directory', fname)
  81.             removeDir(ftp, fname)  # clean subdirectory
  82.             print('directory exited')
  83.     ftp.cwd('..')
  84.     ftp.rmd(dirName)
  85.    
  86. def unlockFs():
  87.     logger.debug("Unlocking File Store")
  88.     try:
  89.         subprocess.check_call(['ssh', backup_prop.remote_user_host, 'rm -f '+backup_prop.remote_fs_path+'/.read-only'])
  90.     except CalledProcessError as cpe:
  91.         logger.error('Error while unlocking File Store: %s'%cpe)
  92.         sendMail("Backup failed! Was unable to unlock File Store!")
  93.         raise BackupFailedException('Error while unlocking File Store!')
  94.     logger.debug("File Store has been unlocked")
  95.    
  96. def uploadToFtp(requiredSize, backupDir, backupTag, backupName):
  97.     freeSpace = 0
  98.    
  99.     logger.debug('Uploading to ftp')
  100.  
  101.     logger.debug('Start checking free space')
  102.  
  103.     # add extra 3Gb to required size as a safe buffer to avoid ftp overload
  104.     while freeSpace <= (requiredSize + 3221225472):
  105.         logger.debug('Connecting to FTP to get login message')
  106.         ftp = FTP(backup_prop.ftp)
  107.         welcomeMes = ftp.login(ftp_user, ftp_pass)
  108.         sizes = re.search(FTP_SIZES_REG_PATTERN, welcomeMes)
  109.         logger.debug('Used space %s Kb', sizes.group(1))
  110.         logger.debug('All space %s Kb', sizes.group(2))
  111.        
  112.         freeSpace = (int(sizes.group(2)) - int(sizes.group(1))) * 1024
  113.         logger.debug('Available space %s bytes', freeSpace)
  114.        
  115.         if freeSpace < (requiredSize + 3221225472):
  116.             logger.debug('Not enough space for new backup: available - %s, required - %s', freeSpace, requiredSize)
  117.             removeOldBackup(ftp)
  118.         logger.debug('Disconnecting FTP')
  119.         ftp.quit()
  120.    
  121.     logger.debug('Checking free space finished')
  122.  
  123.     logger.debug('Connecting to ftp to upload backup files')
  124.  
  125.     ftp = FTP(backup_prop.ftp)
  126.     ftp.login(ftp_user, ftp_pass)
  127.    
  128.     try:
  129.         ftp.cwd('backups')
  130.     except:
  131.         ftp.mkd('backups')
  132.         ftp.cwd('backups')
  133.     try:
  134.         ftp.mkd(backupTag)
  135.     except:
  136.         pass
  137.  
  138.     ftp.cwd(backupTag)
  139.     ftp.mkd(backupName)
  140.     ftp.cwd(backupName)
  141.    
  142.     for f in os.listdir(backupDir):
  143.         fileName = backupDir + f
  144.         logger.debug('Sending file %s'%fileName)
  145.         sendFile = open(fileName, 'r')
  146.         send_cmd = 'STOR %s' % f
  147.         ftp.storbinary(send_cmd, sendFile)
  148.         sendFile.close()
  149.    
  150.     logger.debug('Disconnecting from ftp')
  151.     ftp.quit()
  152.  
  153. ftp_user = backup_prop.ftp_user
  154. ftp_pass = backup_prop.ftp_pass
  155.  
  156. db_user = backup_prop.db_user
  157. db_pass = backup_prop.db_pass
  158.  
  159. ##############  Configure logging  #####################
  160.  
  161. if not os.path.isdir(LOG_DIR):
  162.     os.makedirs(LOG_DIR)
  163.  
  164. logHandler = logging.handlers.TimedRotatingFileHandler(LOG_DIR + '/backup.log', 'd', 1, backupCount=30)
  165. logFormatter = logging.Formatter('%(levelname)s:%(asctime)s - %(message)s')
  166.  
  167. logHandler.setFormatter(logFormatter)
  168.  
  169. logging.basicConfig(format='%(levelname)s:%(asctime)s - %(message)s', level=logging.DEBUG)
  170. logger = logging.getLogger(__name__)
  171. logger.addHandler(logHandler)
  172.  
  173. logHandler.doRollover()
  174.  
  175. #################  Check if another backup is in progress  #####################
  176. print os.path
  177. if os.path.isfile(BACKUP_INPROGRESS):
  178.     logger.error('Another backup process is in progress! Skipping')
  179.     sendMail("Backup skipped: another backup process in progress!")
  180.     sys.exit(1)
  181. open(BACKUP_INPROGRESS,'w').close()
  182.  
  183. ################# Locking main File Store on remote machine ####################
  184. try:
  185.     logger.debug("Locking FIle Store")
  186.     try:
  187.         subprocess.check_call(['ssh', backup_prop.remote_user_host, 'touch '+backup_prop.remote_fs_path+'/.read-only'])
  188.     except CalledProcessError as cpe:
  189.         logger.error('Error while locking File Store: %s'%cpe)
  190.         sendMail("Backup failed! Was unable to lock File Store!")
  191.         raise BackupFailedException('Error while locking File Store!')
  192.      
  193.     logger.debug("File Store has been locked")
  194.     logger.debug("Performing File Store sync")
  195.     try:
  196.         subprocess.check_call(['rsync', '--delete', '-ae', 'ssh', backup_prop.remote_user_host + ':' + backup_prop.remote_fs_path+'/', backup_prop.file_store_path+'/'])
  197.     except CalledProcessError as cpe:
  198.         logger.error('File Store sync failed: %s'%cpe)
  199.         sendMail("Backup failed! Was unable to sync the File Store! Check if the File Store was unlocked!")
  200.         unlockFs()
  201.         raise BackupFailedException("Error during file store sync!")
  202.     logger.debug("File Store sync completed")
  203.  
  204.     logger.debug("Dumping MySQL slave")
  205.    
  206.     try:
  207.         removeFiles(['fulldb.sql'])
  208.         logger.debug("Stopping MySQL slave")
  209.         subprocess.check_call(['mysql', '--user='+backup_prop.db_user, '--password='+backup_prop.db_pass, '-e', 'STOP SLAVE SQL_THREAD;'])
  210.         logger.debug("MySQL slave was stopped, File Store can be unlocked")
  211.         unlockFs()
  212.         logger.debug("Performing databases dump")
  213.         subprocess.check_call([ 'mysqldump', '--all-databases', '--user='+backup_prop.db_user, '--password='+backup_prop.db_pass, '--result-file=fulldb.sql'])
  214.         logger.debug("Dump completed, trying to start MySQL slave")
  215.         subprocess.check_call(['mysql', '--user='+backup_prop.db_user, '--password='+backup_prop.db_pass, '-e', 'START SLAVE SQL_THREAD;'])
  216.         logger.debug("MySQL slave successfully started")
  217.     except CalledProcessError as cpe:
  218.         logger.error('MySQL dump failed: %s'%cpe)
  219.         sendMail("Backup failed! Was unable to dump MySQL! Check if the File Store was unlocked and MySQL slave is up'n'running!")
  220.         logger.debug("Trying to unlock File Store after failure")
  221.         unlockFs()
  222.         logger.debug()
  223.         raise BackupFailedException("Error during MySQL dump!")
  224.     logger.debug("Dumping MySQL slave compleded!")
  225.    
  226.     ###################  KIPOD-PROXY BACKUPING PSQL  ############################
  227.     logger.debug("Perfoming kipod-porxy psql databases dump")
  228.     try:
  229.         removeFiles(['kp_fulldb.sql'])
  230.         subprocess.check_call(['pg_dump',
  231.                                '--dbname=postgresql://' + backup_prop.kp_db_user + ':' + backup_prop.kp_db_pass + '@127.0.0.1:5432/' + backup_prop.kp_db_name,
  232.                                '--exclude-table=' + backup_prop.kp_exluded_table,
  233.                                '--file=kp_fulldb.sql'])
  234.         logger.debug("Dumping PSQL kipod-proxy dadabases completed!")
  235.     except CalledProcessError as cpe:
  236.         logger.error('PSQL dump failed: %s' % cpe)
  237.         # sendMail("Backup failed! Was unable to dump PSQL!")
  238.         logger.debug()
  239.         raise BackupFailedException("Error during PSQL dump!")
  240.  
  241.     #################  Checking backup level  #####################
  242.     logger.debug("Start new back up")
  243.     logger.debug("Checking backup level")
  244.    
  245.     now = datetime.today()
  246.    
  247.     weekday = now.weekday()
  248.     if weekday == 6 or not os.path.isfile(BACKUP_OPT_FILE) or not os.path.isfile(TAR_BACKUP_META) \
  249.                                                             or not os.path.isfile(KP_TAR_BACKUP_META):
  250.         logger.debug("Full backup will be made this time")
  251.         incremental = False
  252.     else:
  253.         try:
  254.             backupOptFile = open(BACKUP_OPT_FILE, 'r')
  255.             backupOpts = backupOptFile.readlines()
  256.             currentBackupLevel = int(backupOpts[0]) + 1
  257.             incremental = True
  258.             backupTag = backupOpts[1]
  259.             backupOptFile.close()
  260.             shutil.copy(TAR_BACKUP_META, TAR_BACKUP_META + '.bak')
  261.             shutil.copy(KP_TAR_BACKUP_META, KP_TAR_BACKUP_META + '.bak')
  262.         except:
  263.             backupOptFile.close()
  264.             logger.error('Error while parsing backup options file: %s. Try to remove [%s] directory and start backup again '
  265.                          '(full backup will be performed)', BACKUP_OPT_FILE, BACKUP_OPT_DIR)
  266.             sendMail("Backup failed! Error while parsing backup options file!")
  267.             raise BackupFailedException("Error while parsing backup options file!")
  268.  
  269.     if not incremental:
  270.         currentBackupLevel = 0
  271.         backupTag = now.strftime(DATE_PATTERN)
  272.         backupName = '%s_full' % backupTag
  273.         shutil.rmtree(BACKUP_OPT_DIR, True)
  274.         os.mkdir(BACKUP_OPT_DIR)
  275.     else:
  276.         backupName = '%s_level_%s' % (backupTag, currentBackupLevel)
  277.      
  278.     logger.debug("Backup level: %s", currentBackupLevel)
  279.     logger.debug("Backup tag(timestamp of the last full backup): %s", backupTag)
  280.     logger.debug("Back up file name: %s", backupName)
  281.    
  282.     localBackupPath = backup_prop.local_backup_path+'/'+backupTag
  283.     try:
  284.         os.makedirs(localBackupPath)
  285.     except:
  286.         pass
  287.    
  288.     logger.debug("Starting backup process")
  289.    
  290.     logger.debug("Starting backup cas.synesis.ru files")
  291.     logFile = open(LOG_DIR + '/tar_mysql.log', 'w')
  292.     try:
  293.         out = subprocess.check_call(
  294.             ['./backup_script.sh', backup_prop.file_store_path, localBackupPath, backupName, TAR_BACKUP_META],
  295.             stdout=logFile, stderr=logFile)
  296.     except CalledProcessError as cpe:
  297.         logHandler.stream.seek(0, 2)
  298.         logger.error('Backup script process exit with error: %s.' % cpe)
  299.         sendMail("Backup  failed! Error while creating tar backup!")
  300.         raise BackupFailedException('Error while creating tar backup!')
  301.     finally:
  302.         logFile.close()
  303.         logHandler.stream.seek(0, 2)
  304.    
  305.     logger.debug("Starting backup kipod-proxy files")
  306.     kp_logFile = open(LOG_DIR + '/kp_tar_mysql.log', 'w')
  307.     try:
  308.         out = subprocess.check_call(
  309.             ['./kp_backup_script.sh', backup_prop.kp_file_store_path, localBackupPath, backupName, KP_TAR_BACKUP_META],
  310.             stdout=kp_logFile, stderr=kp_logFile)
  311.     except CalledProcessError as cpe:
  312.         logHandler.stream.seek(0, 2)
  313.         logger.error('Backup script process exit with error: %s.' % cpe)
  314.         sendMail("Backup  failed! Error while creating tar backup!")
  315.         raise BackupFailedException('Error while creating tar backup!')
  316.     finally:
  317.         kp_logFile.close()
  318.         logHandler.stream.seek(0, 2)
  319.  
  320.     logger.debug("Removing old backups if any")
  321.    
  322.     backupList = os.listdir(backup_prop.local_backup_path)
  323.     # soring reverse so the old backups in the end of the list
  324.     backupList.sort(reverse=True)
  325.     if len(backupList) > 2:
  326.         logger.debug("There are more than 2 backups, removing old ones")
  327.         try:
  328.             for i in range(2, len(backupList)):
  329.                 logger.debug("Removing backup - " + backupList[i])
  330.                 shutil.rmtree(backup_prop.local_backup_path+'/'+backupList[i])
  331.         except:
  332.             logger.error("Backup completed but failed to remove old bakup files!")
  333.             sendMail("Backup completed but failed to remove old bakup files!")
  334.             raise BackupFailedException("Backup completed but failed to remove old bakup files!")
  335.     logger.debug("Backup process finished")
  336.    
  337. #################  Calculating backup size  #########################
  338.  
  339.     logger.debug("Calculating backup size")
  340.     backupDir = '%s/%s/' % (localBackupPath, backupName)
  341.  
  342.     totalSize = 0
  343.     for f in os.listdir(backupDir):
  344.         size = os.path.getsize(backupDir + f)
  345.         totalSize += size
  346.    
  347.     logger.debug('Total backup size: %s bytes', totalSize)
  348.    
  349. ################  Connecting to ftp  #######################
  350.  
  351.     if backup_prop.is_ftp_upload:
  352.         uploadToFtp(totalSize,backupDir,backupTag,backupName)
  353.        
  354.     # Refreshing last backup level value
  355.     backupOptFile = open(BACKUP_OPT_FILE, 'w')
  356.     backupOptFile.writelines([str(currentBackupLevel) + "\n", backupTag])
  357.     backupOptFile.close()
  358.     logger.debug("Backup completed!")
  359.     #sendMail("Backup completed!")
  360. except Exception as e:
  361.     logger.error('Backup failed: %s'%e)
  362.     if not emailSent:
  363.         sendMail("Backup failed!")
  364.     if os.path.isfile(TAR_BACKUP_META + '.bak'):
  365.         shutil.copy(TAR_BACKUP_META + '.bak', TAR_BACKUP_META)
  366.     if os.path.isfile(KP_TAR_BACKUP_META + '.bak'):
  367.         shutil.copy(KP_TAR_BACKUP_META + '.bak', KP_TAR_BACKUP_META)
  368. finally:
  369.     logger.debug("Cleaning temp files")
  370.     removeFiles((TAR_BACKUP_META + '.bak','stderr','stdout',BACKUP_INPROGRESS,'fulldb.sql',KP_TAR_BACKUP_META + '.bak',
  371.                  'kp_fulldb.sql'))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement