lin
2025-07-30 fcd736bf35fd93b563e9bbf594f2aa7b62028cc9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/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()