#!/usr/bin/env python # #******************************************************************************* # #Copyright (c) 2009, Perforce Software, Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #******************************************************************************* # #Author: Stephen Moon #Date: 7/9/2010 #Summary: Backs up Perforce metadata # #******************************************************************************* from subprocess import Popen,PIPE,STDOUT from datetime import date import sys,os,re,time,logging,smtplib #Enable logging of the backup script logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', datefmt='%m-%d %H:%M', filename='p4backup.log', filemode='w') # define a Handler which writes INFO messages or higher to the sys.stderr console = logging.StreamHandler() console.setLevel(logging.INFO) # set a format which is simpler for console use formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') # tell the handler to use this format console.setFormatter(formatter) # add the handler to the root logger logging.getLogger('').addHandler(console) checkForJnlRotation = re.compile(r'^(Checkpointing).+\n(Rotating).*$',re.MULTILINE) checkForP4D = re.compile(r'^(Checkpointing).+\n(Rotating).*$',re.MULTILINE) #define all the environmental variables p4debug = logging.getLogger('p4debug') p4error = logging.getLogger('p4error') path = '/Users/smoon/local/20092/' path2p4d = path + 'p4d' path2p4 = path + 'p4' path2ck = path + 'chkpt/chkpnt' mailhost = 'smtp.perforce.com' server = 'localhost:' port = '20092' jnlDir = '/Users/smoon/local/20092/jnl/' jnlFile = 'p4jnl' logDir = '/Users/smoon/local/20092/log/' logFile = 'p4log' P4USER = 'smoon' P4ROOT = '/Users/smoon/local/20092' P4CHARSET = 'utf8' #set it to empty string if your server is not Unicode P4PORT = server + port P4JOURNAL = jnlDir + jnlFile P4LOG = logDir + logFile os.environ['P4ROOT'] = P4ROOT os.environ['P4JOURNAL']= P4JOURNAL os.environ['P4PORT'] = P4PORT os.environ['P4LOG'] = P4LOG os.environ['P4USER'] = P4USER os.environ['P4CHARSET'] = P4CHARSET def emailAdmin(status): #failure is 1 and success is 0 f = open('p4backup.log') line = f.readline() fromaddr = P4USER + '@' + mailhost toaddr = [P4USER + '@' + mailhost] msg = "From: %s\nTo: %s\n\n" % (fromaddr, ",".join(toaddr)) if status == 1: msg = "Subject: Perforce Checkpoint FAILED on %s\n\n" % (date.today()) elif status == 0: msg = "Subject: Perforce Checkpoint SUCCEEDED on %s\n\n" % (date.today()) else: msg = "Subject: New Perforce Checkpoint Substantially \ Smaller than the Previous on %s\n\n" % (date.today()) while line: msg += line line = f.readline() f.close() try: server = smtplib.SMTP(mailhost) except: p4error.exception('Unable to create mailport') exit(1) try: server.sendmail(fromaddr,toaddr,msg) except: p4error.exception('Unable to send an email') exit(1) try: server.quit() except: p4error.exception('Unable to quit mail') exit(1) def checkJournal(jnl): if os.path.isfile(jnl): pass else: p4error.exception("The running journal: " + jnl + " does not exist") emailAdmin(1) exit(1) def stopServer(path2p4): #stop the server. May need to be customized stopServer = [path2p4,'admin','stop'] p = Popen(stopServer,stdout=PIPE,stderr=PIPE) stopOut,stopErr = p.communicate() p.stdout.close() p.stderr.close() if stopErr == "": print('Server stopped successfully!\n') p4debug.debug('Server stopped successfully...\n') else: sys.stderr.write(stopErr) p4error.exception('Error stopping the server: \n%s\n' % stopErr) emailAdmin(1) exit(1) def takeCheckpoint(path2p4d,path2ck,checkForJnlRotation): #After the server is stopped, a checkpoint is taken #This creates a compressed checkpoint takeCkpnt = [path2p4d,'-z','-jc',path2ck] p1 = Popen(takeCkpnt,stdout=PIPE,stderr=PIPE) chkOut,chkErr = p1.communicate() p1.stdout.close() p1.stderr.close() if chkOut != "": jnlRotate = checkForJnlRotation.match(chkOut) if jnlRotate.group(1) and jnlRotate.group(2): print(chkOut) p4debug.debug('Taking a checkpoint: \n%s' % chkOut) p4debug.debug("Checkpoint taken successfully\n") else: sys.stderr.write(chkErr) p4error.exception('Error while taking a checkpoint: \n%s' % chkErr) p4error.exception("Checkpoint failed\n") emailAdmin(1) exit(1) else: sys.stderr.write(chkErr) p4error.exception('Error while taking a checkpoint: \n%s' % chkErr) p4error.exception("Checkpoint failed\n") emailAdmin(1) exit(1) def restartServer(path2p4d,status): #This runs in a no-wait mode, so that it doesn't hang the script. #p4pid contains the PID of the server process. It is an indication #that the Perforce server has started successfully. p4pid = Popen(path2p4d,shell=True,stdout=PIPE,stderr=PIPE).pid if p4pid: print 'Server started successfully!\n' p4debug.debug('Starting the server... PID: %d' % p4pid) p4debug.debug('Server started successfully!\n') emailAdmin(0) elif p4pid and status: p4debug.debug('New checkpoint much smaller than the previous') emailAdmin(status) else: p4error.exception('Failed to start the server.') emailAdmin(1) exit(1) def getJnlNumber(): getJnlNum = [path2p4,'counter','journal'] p = Popen(getJnlNum,stdout=PIPE,stderr=PIPE) jnlOut, jnlErr = p.communicate() p.stdout.close() p.stderr.close() if jnlErr == "": #print('Journal Ctr Value: %s\n' % jnlOut) p4debug.debug('Journal Counter: ' + jnlOut) else: sys.stderr.write(jnlErr) p4error.exception('Unable to get the journal counter value: \n%s' % jnlErr) emailAdmin(1) exit(1) return jnlOut.strip() def deleteOldBackup(jnlNumber): if int(jnlNumber) > 10: for i in range(int(jnlNumber) - 10): ckpFileName = path2ck + ".ckp." + str(i) + ".gz" jnlFileName = path2ck + ".jnl." + str(i - 1) + ".gz" if os.path.isfile(ckpFileName): os.remove(path2ck + ".ckp." + str(i) + ".gz") else: pass if os.path.isfile(jnlFileName): os.remove(path2ck + ".jnl." + str(i - 1) + ".gz") else: pass def comparePrevNextCkpt(jnlNumber): prevCheckpoint = path2ck + ".ckp." + jnlNumber + ".gz" nextCheckpoint = path2ck + ".ckp." + str(int(jnlNumber) + 1) + ".gz" prevCkptSize = os.path.getsize(prevCheckpoint) nextCkptSize = os.path.getsize(nextCheckpoint) print("PrevCkpt Filename: %s" % prevCheckpoint) print("File Size: %f\n" % prevCkptSize) print("NewCkpt Filename: %s" % nextCheckpoint) print("File Size: %f\n" % nextCkptSize) if ((nextCkptSize - prevCkptSize) < 0 and prevCkptSize*0.10 < (prevCkptSize - nextCkptSize)): p4debug.debug("New checkpoint substantially smaller than \ the previous checkpoint"); return 2 p4debug.debug("PrevCkpt Filename: %s" % prevCheckpoint) p4debug.debug("File Size: %f" % prevCkptSize) p4debug.debug("NewCkpt Filename: %s" % nextCheckpoint) p4debug.debug("File Size: %f\n" % nextCkptSize) return 0 def main(): checkJournal(P4JOURNAL) jnlNumber = getJnlNumber() deleteOldBackup(jnlNumber) stopServer(path2p4) takeCheckpoint(path2p4d,path2ck,checkForJnlRotation) status = comparePrevNextCkpt(jnlNumber) restartServer(path2p4d,status) if __name__ == '__main__': main()