2
# -*- mode: python; indent-tabs-mode: nil; -*-
3
# vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
5
# Copyright (C) 2010,2011 Patrick Crews
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
""" mysqld.py: code to allow a serverManager
23
to provision and start up a mysqld server object
35
from ConfigParser import RawConfigParser
39
from lib.server_mgmt.server import Server
40
from lib.util.mysql_methods import execute_query
42
class mysqlServer(Server):
43
""" represents a mysql server, its possessions
44
(datadir, ports, etc), and methods for controlling
47
TODO: create a base server class that contains
48
standard methods from which we can inherit
49
Currently there are definitely methods / attr
54
def __init__( self, name, server_manager, code_tree, default_storage_engine
55
, server_options, requester, test_executor, workdir_root):
56
super(mysqlServer, self).__init__( name
59
, default_storage_engine
64
self.preferred_base_port = 9306
67
self.mysqldump = self.code_tree.mysqldump
68
self.mysqladmin = self.code_tree.mysqladmin
69
self.mysql_client = self.code_tree.mysql_client
70
self.mysqlimport = self.code_tree.mysqlimport
71
self.mysqlslap = self.code_tree.mysqlslap
72
self.mysql_upgrade = self.code_tree.mysql_upgrade
73
self.server_path = self.code_tree.mysql_server
74
self.mysql_client_path = self.code_tree.mysql_client
77
self.langdir = self.code_tree.langdir
78
self.charsetdir = self.code_tree.charsetdir
79
self.bootstrap_file = self.code_tree.bootstrap_path
80
self.bootstrap_cmd = None
82
# MySQL 5.0 and 5.1 servers use --language argument vs. lc-message-dir
83
# We check and set things appropriately for such cases
84
self.langstring = "--lc-messages-dir=%s" %(self.langdir)
85
if self.version.startswith('5.1') or self.version.startswith('5.0'):
86
self.langstring = "--language=%s" %(os.path.join(self.langdir
91
self.port_block = self.system_manager.port_manager.get_port_block( self.name
92
, self.preferred_base_port
94
self.master_port = self.port_block[0]
96
# Generate our working directories
97
self.dirset = { 'var_%s' %(self.name): {'std_data_ln':( os.path.join(self.code_tree.testdir,'std_data'))
101
,'master-data': { 'test':None
106
self.workdir = self.system_manager.create_dirset( workdir_root
108
self.vardir = self.workdir
109
self.tmpdir = os.path.join(self.vardir,'tmp')
110
self.rundir = os.path.join(self.vardir,'run')
111
self.logdir = os.path.join(self.vardir,'log')
112
self.std_data = os.path.join(self.vardir,'std_data_ln')
113
self.datadir = os.path.join(self.vardir,'master-data')
115
self.error_log = os.path.join(self.logdir,('error.log'))
116
self.bootstrap_log = os.path.join(self.logdir,('bootstrap.log'))
117
self.pid_file = os.path.join(self.rundir,('%s.pid' %(self.name)))
118
self.socket_file = os.path.join(self.vardir, ('%s.sock' %(self.name)))
119
if len(self.socket_file) > 107:
120
# MySQL has a limitation of 107 characters for socket file path
121
# we copy the mtr workaround of creating one in /tmp
122
self.logging.verbose("Default socket file path: %s" %(self.socket_file))
123
self.socket_file = "/tmp/%s_%s.%s.sock" %(self.system_manager.uuid
126
self.logging.verbose("Changing to alternate: %s" %(self.socket_file))
127
self.timer_file = os.path.join(self.logdir,('timer'))
128
self.general_log_file = os.path.join(self.logdir,'mysqld.log')
129
self.slow_query_log_file = os.path.join(self.logdir,'mysqld-slow.log')
130
self.cnf_file = os.path.join(self.vardir,'my.cnf')
132
self.snapshot_path = os.path.join(self.tmpdir,('snapshot_%s' %(self.master_port)))
133
# We want to use --secure-file-priv = $vardir by default
134
# but there are times / tools when we need to shut this off
135
if self.no_secure_file_priv:
136
self.secure_file_string = ''
138
self.secure_file_string = "--secure-file-priv='%s'" %(self.vardir)
140
self.initialize_databases()
141
self.take_db_snapshot()
143
self.logging.debug_class(self)
147
""" We print out some general useful info """
148
report_values = [ 'name'
154
self.logging.info("%s server:" %(self.owner))
155
for key in report_values:
156
value = vars(self)[key]
157
self.logging.info("%s: %s" %(key.upper(), value))
159
def initialize_databases(self):
160
""" Do the voodoo required to have a working database setup.
161
For MySQL, this is calling the server with the
162
--bootstrap argument. We generate the bootstrap
163
file during codeTree intialization as the file is standard for
164
all MySQL servers that are spawned from a single codeTree
168
# generate the bootstrap startup command
169
if not self.bootstrap_cmd:
170
mysqld_args = [ "--no-defaults"
172
, "--basedir=%s" %(self.code_tree.basedir)
173
, "--datadir=%s" %(self.datadir)
174
, "--loose-skip-falcon"
175
, "--loose-skip-ndbcluster"
176
, "--tmpdir=%s" %(self.tmpdir)
179
, "--character-sets-dir=%s" %(self.charsetdir)
181
# We add server_path into the mix this way as we
182
# may alter how we store / handle server args later
183
mysqld_args.insert(0,self.server_path)
184
self.bootstrap_cmd = " ".join(mysqld_args)
185
# execute our command
186
bootstrap_log = open(self.bootstrap_log,'w')
187
# open our bootstrap file
188
bootstrap_in = open(self.bootstrap_file,'r')
189
bootstrap_subproc = subprocess.Popen( self.bootstrap_cmd
192
, stdout=bootstrap_log
193
, stderr=bootstrap_log
195
bootstrap_subproc.wait()
197
bootstrap_log.close()
198
bootstrap_retcode = bootstrap_subproc.returncode
199
if bootstrap_retcode:
200
self.logging.error("Received retcode: %s executing command: %s"
201
%(bootstrap_retcode, self.bootstrap_cmd))
202
self.logging.error("Check the bootstrap log: %s" %(self.bootstrap_log))
206
def get_start_cmd(self):
207
""" Return the command string that will start up the server
208
as desired / intended
212
# We make ourselves bug-compatible with MTR / xtrabackup
214
if platform.system() != 'Windows' and os.geteuid() == 0:
215
self.server_options.append("--user=root")
216
server_args = [ "--no-defaults"
217
, self.process_server_options()
218
, "--open-files-limit=1024"
220
, "--character-set-server=latin1"
221
, "--connect-timeout=60"
222
, "--log-bin-trust-function-creators=1"
223
, "--key_buffer_size=1M"
224
, "--sort_buffer=256K"
225
, "--max_heap_table_size=1M"
226
, "--loose-innodb_data_file_path=ibdata1:10M:autoextend"
227
, "--loose-innodb_buffer_pool_size=8M"
228
, "--loose-innodb_write_io_threads=2"
229
, "--loose-innodb_read_io_threads=2"
230
, "--loose-innodb_log_buffer_size=1M"
231
, "--loose-innodb_log_file_size=5M"
232
, "--loose-innodb_additional_mem_pool_size=1M"
233
, "--loose-innodb_log_files_in_group=2"
234
, "--slave-net-timeout=120"
235
, "--log-bin=%s" %(os.path.join(self.logdir,"mysqld-bin"))
236
, "--binlog-format=ROW"
237
, "--loose-enable-performance-schema"
238
, "--loose-performance-schema-max-mutex-instances=10000"
239
, "--loose-performance-schema-max-rwlock-instances=10000"
240
, "--loose-performance-schema-max-table-instances=500"
241
, "--loose-performance-schema-max-table-handles=1000"
242
, "--loose-enable-performance-schema"
243
, "--basedir=%s" %(self.code_tree.basedir)
244
, "--datadir=%s" %(self.datadir)
245
, "--tmpdir=%s" %(self.tmpdir)
246
, "--character-sets-dir=%s" %(self.charsetdir)
248
, "--ssl-ca=%s" %(os.path.join(self.std_data,'cacert.pem'))
249
, "--ssl-cert=%s" %(os.path.join(self.std_data,'server-cert.pem'))
250
, "--ssl-key=%s" %(os.path.join(self.std_data,'server-key.pem'))
251
, "--port=%d" %(self.master_port)
252
, "--socket=%s" %(self.socket_file)
253
, "--pid-file=%s" %(self.pid_file)
254
, "--default-storage-engine=%s" %(self.default_storage_engine)
255
# server-id maybe needs fixing, but we want to avoid
256
# the server-id=0 and no replication thing...
257
, "--server-id=%d" %(self.get_numeric_server_id()+1)
258
, self.secure_file_string
261
if not self.version.startswith('5.0'):
262
server_args += [ "--binlog-direct-non-transactional-updates"
264
, "--general_log_file=%s" %(self.general_log_file)
265
, "--slow_query_log=1"
266
, "--slow_query_log_file=%s" %(self.slow_query_log_file)
268
self.gen_cnf_file(server_args)
271
server_args.append('--gdb')
272
return self.system_manager.handle_gdb_reqs(self, server_args)
274
return "%s %s %s & " % ( self.cmd_prefix
276
, " ".join(server_args)
280
def get_stop_cmd(self):
281
""" Return the command that will shut us down """
283
return "%s --no-defaults --user=root --port=%d --host=127.0.0.1 --protocol=tcp shutdown " %(self.mysqladmin, self.master_port)
286
def get_ping_cmd(self):
287
"""Return the command string that will
288
ping / check if the server is alive
292
return '%s --no-defaults --user=root --port=%d --host=127.0.0.1 --protocol=tcp ping' % (self.mysqladmin, self.master_port)
294
def is_started(self):
295
""" Determine if the server is up and running -
296
this may vary from server type to server type
300
# We experiment with waiting for a pid file to be created vs. pinging
301
# This is what test-run.pl does and it helps us pass logging_stats tests
302
# while not self.ping_server(server, quiet=True) and timer != timeout:
304
return self.system_manager.find_path( [self.pid_file]
307
def gen_cnf_file(self, server_args):
308
""" We generate a .cnf file for the server based
309
on the arguments. We currently don't use
310
this for much, but xtrabackup uses it, so
311
we must produce one. This could also be
312
helpful for testing / etc
316
config_file = open(self.cnf_file,'w')
317
config_file.write('[mysqld]')
318
for server_arg in server_args:
319
# We currently have a list of string values
320
# We need to remove any '--' stuff
321
server_arg = server_arg.replace('--','')+'\n'
322
config_file.write(server_arg)
325
def set_master(self, master_server, get_cur_log_pos = True):
326
""" We do what is needed to set the master_server
327
as the replication master
331
if self.status: # we are running and can do things!
332
# Get master binlog info
333
retcode, master_binlog_file, master_binlog_pos = master_server.get_binlog_info()
335
if not get_cur_log_pos:
336
master_binlog_pos = 0
338
return retcode, master_binlog_file #contains error msg on failure
340
# update our slave's master info
341
query = ("CHANGE MASTER TO "
342
"MASTER_HOST='127.0.0.1',"
343
"MASTER_USER='root',"
344
"MASTER_PASSWORD='',"
346
"MASTER_LOG_FILE='%s',"
347
"MASTER_LOG_POS=%d" % ( master_server.master_port
349
, int(master_binlog_pos)))
350
retcode, result_set = execute_query(query, self)
352
msg = ("Could not set slave: %s.%s\n"
354
"Returned result: %s" %( self.owner
362
self.need_to_set_master = False
364
self.need_to_set_master = True
365
self.master = master_server
368
def get_slave_status(self):
369
query = "SHOW SLAVE STATUS"
370
retcode, result_set = execute_query(query, self)
371
result_set = result_set[0]
373
slave_data = { 'slave_io_state':result_set[0]
374
, 'master_host':result_set[1]
375
, 'master_user':result_set[2]
376
, 'master_port':result_set[3]
377
, 'connect_retry':result_set[4]
378
, 'master_log_file':result_set[5]
379
, 'read_master_log_pos':result_set[6]
380
, 'relay_log_file':result_set[7]
381
, 'relay_log_pos':result_set[8]
382
, 'relay_master_log_file':result_set[9]
383
, 'slave_io_running':result_set[10]
384
, 'slave_sql_running':result_set[11]
385
, 'replicate_do_db':result_set[12]
386
, 'replicate_ignore_db':result_set[13]
387
, 'replicate_do_table':result_set[14]
388
, 'replicate_ignore_table':result_set[15]
389
, 'replicate_wild_do_table':result_set[16]
390
, 'replicate_wild_ignore_table':result_set[17]
391
, 'last_errno':result_set[18]
392
, 'last_error':result_set[19]
393
, 'skip_counter':result_set[20]
394
, 'exec_master_log_pos':result_set[21]
395
, 'relay_log_space':result_set[22]
396
, 'until_condition':result_set[23]
397
, 'until_log_file':result_set[24]
398
, 'until_log_pos':result_set[25]
399
, 'master_ssl_allowed':result_set[26]
400
, 'master_ssl_ca_file':result_set[27]
401
, 'master_ssl_ca_path':result_set[28]
402
, 'master_ssl_cert':result_set[29]
403
, 'master_ssl_cipher':result_set[30]
404
, 'master_ssl_key':result_set[31]
405
, 'seconds_behind_master':result_set[32]
406
, 'master_ssl_verify_server_cert':result_set[33]
407
, 'last_io_errno':result_set[34]
408
, 'last_io_error':result_set[35]
409
, 'last_sql_errno':result_set[36]
410
, 'last_sql_error':result_set[37]
411
#, 'replicate_ignore_server_ids':result_set[38]
418
def get_binlog_info(self):
419
""" We try to get binlog information for the server """
420
query = "SHOW MASTER STATUS"
421
retcode, result_set = execute_query(query, self)
423
binlog_file = result_set[0][0]
424
binlog_pos = result_set[0][1]
426
binlog_file = result_set
427
binlog_pos = result_set
428
return retcode, binlog_file, binlog_pos
430
def slave_stop(self):
431
""" We issue STOP SLAVE and wait for IO and SQL threads to stop """
434
retcode, result = execute_query(query, self)
435
slave_status = self.get_slave_status()
436
while slave_status['slave_io_running'] == 'Yes' or slave_status['slave_sql_running'] == 'Yes':
437
slave_status = self.get_slave_status()
439
def slave_start(self):
440
""" We issue START SLAVE and wait for IO and SQL threads to start """
441
query = "START SLAVE"
443
retcode, result = execute_query(query, self)
444
slave_status = self.get_slave_status()
445
while slave_status['slave_io_running'] == 'No' or slave_status['slave_sql_running'] == 'No':
446
time.sleep(decrement)
447
slave_status = self.get_slave_status()
450
def wait_sync_with_slaves(self, slave_list, timeout=60):
451
""" We scan through our slave list and make sure we are synched up
452
before moving on with the test
456
retcode, master_binlog_file, master_binlog_pos = self.get_binlog_info()
458
for idx, slave_server in enumerate(slave_list):
459
slave_status_data = slave_server.get_slave_status()
460
read_master_log_pos = slave_status_data['read_master_log_pos']
461
exec_master_log_pos = slave_status_data['exec_master_log_pos']
462
seconds_behind_master = slave_status_data['seconds_behind_master']
463
slave_io_state = slave_status_data['slave_io_state']
464
# This test subject to change as I learn more ; )
465
#if master_binlog_pos == exec_master_log_pos:
466
if exec_master_log_pos == read_master_log_pos and slave_io_state == 'Waiting for master to send event':
467
#print 'Server: %s.%s' %(self.owner, self.name)
468
#print 'Master_binlog_file: %s' %master_binlog_file
469
#print 'Master_binlog_pos: %s' %master_binlog_pos
470
#print 'Exec_master_log_pos: %s' %exec_master_log_pos
471
#print 'Read_master_log_pos: %s' %read_master_log_pos
472
#if read_master_log_pos > master_binlog_pos:
473
#for key, item in slave_status_data.items():
474
# print "%s:%s" %(key, item)
476
# It is normally a no-no to remove items from a list
477
# mid-iteration, but this is a chance to break that rule
481
def slave_ready(self, master_server):
485
def get_innodb_version(self):
486
""" SHOW VARIABLES LIKE innodb_version
487
mostly used as a check to ensure if a
488
test should/shouldn't be executed
492
query = "SHOW VARIABLES LIKE 'innodb_version'"
493
retcode, result = execute_query(query, self)
494
return retcode, result
496
def get_xtradb_version(self):
497
""" Return the xtradb version or None """
499
retcode, result = self.get_innodb_version()
500
# result format = (('innodb_version', '1.1.6-20.1'),)
502
innodb_version = result[0][1]
503
split_data = innodb_version.split('-')
504
if len(split_data) > 1:
505
return split_data[-1]