142 lines
5 KiB
Python
Executable file
142 lines
5 KiB
Python
Executable file
#!/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-build-alerts+db-replica-checker@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()
|