1#!/usr/bin/python 2# 3# Copyright 2010 Google Inc. All Rights Reserved. 4 5"""Tool to check the data consistency between master autotest db and replica. 6 7This tool will issue 'show master status;' and 'show slave status;' commands to 8two replicated databases to compare its log position. 9 10It will also take a delta command line argument to allow certain time delay 11between master and slave. If the delta of two log positions falls into the 12defined range, it will be treated as synced. 13 14It will send out an email notification upon any problem if specified an --to 15argument. 16""" 17 18import getpass 19import MySQLdb 20import optparse 21import os 22import socket 23import sys 24 25import common 26from autotest_lib.client.common_lib import global_config 27 28 29c = global_config.global_config 30_section = 'AUTOTEST_WEB' 31DATABASE_HOST = c.get_config_value(_section, "host") 32REPLICA_DATABASE_HOST = c.get_config_value(_section, "readonly_host") 33DATABASE_NAME = c.get_config_value(_section, "database") 34DATABASE_USER = c.get_config_value(_section, "user") 35DATABASE_PASSWORD = c.get_config_value(_section, "password") 36SYSTEM_USER = 'chromeos-test' 37 38 39def ParseOptions(): 40 parser = optparse.OptionParser() 41 parser.add_option('-d', '--delta', help='Difference between master and ' 42 'replica db', type='int', dest='delta', default=0) 43 parser.add_option('--to', help='Comma separated Email notification TO ' 44 'recipients.', dest='to', type='string', default='') 45 parser.add_option('--cc', help='Comma separated Email notification CC ' 46 'recipients.', dest='cc', type='string', default='') 47 parser.add_option('-t', '--test-mode', help='skip common group email', 48 dest='testmode', action='store_true', default=False) 49 options, _ = parser.parse_args() 50 return options 51 52 53def FetchMasterResult(): 54 master_conn = MySQLdb.connect(host=DATABASE_HOST, 55 user=DATABASE_USER, 56 passwd=DATABASE_PASSWORD, 57 db=DATABASE_NAME ) 58 cursor = master_conn.cursor(MySQLdb.cursors.DictCursor) 59 cursor.execute ("show master status;") 60 master_result = cursor.fetchone() 61 master_conn.close() 62 return master_result 63 64 65def FetchSlaveResult(): 66 replica_conn = MySQLdb.connect(host=REPLICA_DATABASE_HOST, 67 user=DATABASE_USER, 68 passwd=DATABASE_PASSWORD, 69 db=DATABASE_NAME ) 70 cursor = replica_conn.cursor(MySQLdb.cursors.DictCursor) 71 cursor.execute ("show slave status;") 72 slave_result = cursor.fetchone() 73 replica_conn.close() 74 return slave_result 75 76 77def RunChecks(options, master_result, slave_result): 78 master_pos = master_result['Position'] 79 slave_pos = slave_result['Read_Master_Log_Pos'] 80 if (master_pos - slave_pos) > options.delta: 81 return 'DELTA EXCEEDED: master=%s, slave=%s' % (master_pos, slave_pos) 82 if slave_result['Last_SQL_Error'] != '': 83 return 'SLAVE Last_SQL_Error' 84 if slave_result['Slave_IO_State'] != 'Waiting for master to send event': 85 return 'SLAVE Slave_IO_State' 86 if slave_result['Last_IO_Error'] != '': 87 return 'SLAVE Last_IO_Error' 88 if slave_result['Slave_SQL_Running'] != 'Yes': 89 return 'SLAVE Slave_SQL_Running' 90 if slave_result['Slave_IO_Running'] != 'Yes': 91 return 'SLAVE Slave_IO_Running' 92 return None 93 94 95def ShowStatus(options, master_result, slave_result, msg): 96 summary = 'Master (%s) and slave (%s) databases are out of sync.' % ( 97 DATABASE_HOST, REPLICA_DATABASE_HOST) + msg 98 if not options.to: 99 print summary 100 print 'Master status:' 101 print str(master_result) 102 print 'Slave status:' 103 print str(slave_result) 104 else: 105 email_to = ['%s@google.com' % to.strip() for to in options.to.split(',')] 106 email_cc = [] 107 if options.cc: 108 email_cc.extend( 109 '%s@google.com' % cc.strip() for cc in options.cc.split(',')) 110 if getpass.getuser() == SYSTEM_USER and not options.testmode: 111 email_cc.append('chromeos-build-alerts+db-replica-checker@google.com') 112 body = ('%s\n\n' 113 'Master (%s) status:\n%s\n\n' 114 'Slave (%s) status:\n%s' % (summary, DATABASE_HOST, master_result, 115 REPLICA_DATABASE_HOST, slave_result)) 116 p = os.popen('/usr/sbin/sendmail -t', 'w') 117 p.write('To: %s\n' % ','.join(email_to)) 118 if email_cc: 119 p.write('Cc: %s\n' % ','.join(email_cc)) 120 121 p.write('Subject: Inconsistency detected in cautotest DB replica on %s.\n' 122 % socket.gethostname()) 123 p.write('Content-Type: text/plain') 124 p.write('\n') # blank line separating headers from body 125 p.write(body) 126 p.write('\n') 127 return_code = p.close() 128 if return_code is not None: 129 print 'Sendmail exit status %s' % return_code 130 131 132def main(): 133 options = ParseOptions() 134 master_result = FetchMasterResult() 135 slave_result = FetchSlaveResult() 136 problem_msg = RunChecks(options, master_result, slave_result) 137 if problem_msg: 138 ShowStatus(options, master_result, slave_result, problem_msg) 139 sys.exit(-1) 140 141if __name__ == '__main__': 142 main() 143