#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. """Tool to check the data consistency between master autotest db and replica. This tool will issue 'show master status;' and 'show slave status;' commands to two replicated databases to compare its log position. It will also take a delta command line argument to allow certain time delay between master and slave. If the delta of two log positions falls into the defined range, it will be treated as synced. It will send out an email notification upon any problem if specified an --to argument. """ import getpass import MySQLdb import optparse import os import socket import sys import common from autotest_lib.client.common_lib import global_config c = global_config.global_config _section = 'AUTOTEST_WEB' DATABASE_HOST = c.get_config_value(_section, "host") REPLICA_DATABASE_HOST = c.get_config_value(_section, "readonly_host") DATABASE_NAME = c.get_config_value(_section, "database") DATABASE_USER = c.get_config_value(_section, "user") DATABASE_PASSWORD = c.get_config_value(_section, "password") SYSTEM_USER = 'chromeos-test' def ParseOptions(): parser = optparse.OptionParser() parser.add_option('-d', '--delta', help='Difference between master and ' 'replica db', type='int', dest='delta', default=0) parser.add_option('--to', help='Comma separated Email notification TO ' 'recipients.', dest='to', type='string', default='') parser.add_option('--cc', help='Comma separated Email notification CC ' 'recipients.', dest='cc', type='string', default='') parser.add_option('-t', '--test-mode', help='skip common group email', dest='testmode', action='store_true', default=False) options, _ = parser.parse_args() return options def FetchMasterResult(): master_conn = MySQLdb.connect(host=DATABASE_HOST, user=DATABASE_USER, passwd=DATABASE_PASSWORD, db=DATABASE_NAME ) cursor = master_conn.cursor(MySQLdb.cursors.DictCursor) cursor.execute ("show master status;") master_result = cursor.fetchone() master_conn.close() return master_result def FetchSlaveResult(): replica_conn = MySQLdb.connect(host=REPLICA_DATABASE_HOST, user=DATABASE_USER, passwd=DATABASE_PASSWORD, db=DATABASE_NAME ) cursor = replica_conn.cursor(MySQLdb.cursors.DictCursor) cursor.execute ("show slave status;") slave_result = cursor.fetchone() replica_conn.close() return slave_result def RunChecks(options, master_result, slave_result): master_pos = master_result['Position'] slave_pos = slave_result['Read_Master_Log_Pos'] if (master_pos - slave_pos) > options.delta: return 'DELTA EXCEEDED: master=%s, slave=%s' % (master_pos, slave_pos) if slave_result['Last_SQL_Error'] != '': return 'SLAVE Last_SQL_Error' if slave_result['Slave_IO_State'] != 'Waiting for master to send event': return 'SLAVE Slave_IO_State' if slave_result['Last_IO_Error'] != '': return 'SLAVE Last_IO_Error' if slave_result['Slave_SQL_Running'] != 'Yes': return 'SLAVE Slave_SQL_Running' if slave_result['Slave_IO_Running'] != 'Yes': return 'SLAVE Slave_IO_Running' return None def ShowStatus(options, master_result, slave_result, msg): summary = 'Master (%s) and slave (%s) databases are out of sync.' % ( DATABASE_HOST, REPLICA_DATABASE_HOST) + msg if not options.to: print summary print 'Master status:' print str(master_result) print 'Slave status:' print str(slave_result) else: email_to = ['%s@google.com' % to.strip() for to in options.to.split(',')] email_cc = [] if options.cc: email_cc.extend( '%s@google.com' % cc.strip() for cc in options.cc.split(',')) if getpass.getuser() == SYSTEM_USER and not options.testmode: email_cc.append('chromeos-test-cron@google.com') body = ('%s\n\n' 'Master (%s) status:\n%s\n\n' 'Slave (%s) status:\n%s' % (summary, DATABASE_HOST, master_result, REPLICA_DATABASE_HOST, slave_result)) p = os.popen('/usr/sbin/sendmail -t', 'w') p.write('To: %s\n' % ','.join(email_to)) if email_cc: p.write('Cc: %s\n' % ','.join(email_cc)) p.write('Subject: Inconsistency detected in cautotest DB replica on %s.\n' % socket.gethostname()) p.write('Content-Type: text/plain') p.write('\n') # blank line separating headers from body p.write(body) p.write('\n') return_code = p.close() if return_code is not None: print 'Sendmail exit status %s' % return_code def main(): options = ParseOptions() master_result = FetchMasterResult() slave_result = FetchSlaveResult() problem_msg = RunChecks(options, master_result, slave_result) if problem_msg: ShowStatus(options, master_result, slave_result, problem_msg) sys.exit(-1) if __name__ == '__main__': main()