blob: 002f17fdf06cab3af22819ec3725dcfe8ac47183 [file] [log] [blame]
Scott Zawalski20a9b582011-11-21 11:49:40 -08001#!/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-test-cron@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()