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
""" server.py: generic server object used by the server
23
manager. This contains the generic methods for all
24
servers. Specific types (Drizzle, MySQL, etc) should
34
from lib.util.mysql_methods import execute_query
37
""" the server class from which other servers
38
will inherit - contains generic methods
39
certain methods will be overridden by more
48
, default_storage_engine
51
, test_executor = None
52
, workdir_root = None):
53
self.skip_keys = [ 'server_manager'
56
, 'preferred_base_port'
57
, 'no_secure_file_priv'
58
, 'secure_file_string'
61
self.debug = server_manager.debug
62
self.verbose = server_manager.verbose
64
self.timer_increment = .5
65
self.owner = requester
66
self.test_executor = test_executor
67
self.server_options = server_options
68
self.default_storage_engine = default_storage_engine
69
self.server_manager = server_manager
70
# We register with server_manager asap
71
self.server_manager.log_server(self, requester)
73
self.system_manager = self.server_manager.system_manager
74
self.code_tree = code_tree
75
self.version = self.code_tree.server_version
76
self.type = self.code_tree.type
77
self.valgrind = self.system_manager.valgrind
78
self.gdb = self.system_manager.gdb
80
self.valgrind_time_buffer = 10
82
self.valgrind_time_buffer = 1
83
self.cmd_prefix = self.system_manager.cmd_prefix
84
self.logging = self.system_manager.logging
85
self.no_secure_file_priv = self.server_manager.no_secure_file_priv
87
self.status = 0 # stopped, 1 = running
89
self.failed_test = 0 # was the last test a failure? our state is suspect
90
self.server_start_timeout = 60 * self.valgrind_time_buffer
92
self.ip_address = '127.0.0.1'
93
self.need_reset = False
95
self.need_to_set_master = False
99
def initialize_databases(self):
100
""" Call schemawriter to make db.opt files """
104
for database in databases:
105
db_path = os.path.join(self.datadir,'local',database,'db.opt')
106
cmd = "%s %s %s" %(self.schemawriter, database, db_path)
107
self.system_manager.execute_cmd(cmd)
109
def process_server_options(self):
110
"""Consume the list of options we have been passed.
111
Return a string with them joined
115
return " ".join(self.server_options)
117
def take_db_snapshot(self):
118
""" Take a snapshot of our vardir for quick restores """
120
self.logging.info("Taking clean db snapshot...")
121
if os.path.exists(self.snapshot_path):
122
# We need to remove an existing path as python shutil
123
# doesn't want an existing target
124
self.system_manager.remove_dir(self.snapshot_path)
125
self.system_manager.copy_dir(self.datadir, self.snapshot_path)
127
def restore_snapshot(self):
128
""" Restore from a stored snapshot """
130
if not os.path.exists(self.snapshot_path):
131
self.logging.error("Could not find snapshot: %s" %(self.snapshot_path))
132
self.system_manager.remove_dir(self.datadir)
133
self.system_manager.copy_dir(self.snapshot_path, self.datadir)
135
def is_started(self):
136
""" Is the server running? Particulars are server-dependent """
138
return "You need to implement is_started"
140
def get_start_cmd(self):
141
""" Return the command the server_manager can use to start me """
143
return "You need to implement get_start_cmd"
145
def get_stop_cmd(self):
146
""" Return the command the server_manager can use to stop me """
148
return "You need to implement get_stop_cmd"
150
def get_ping_cmd(self):
151
""" Return the command that can be used to 'ping' me
152
Very similar to is_started, but different
154
Determining if a server is still running (ping)
155
may differ from the method used to determine
160
return "You need to implement get_ping_cmd"
162
def set_master(self, master_server, get_cur_log_pos = True):
163
""" Do what is needed to set the server to replicate
164
/ consider the master_server as its 'master'
168
return "You need to implement set_master"
171
""" Cleanup - just free ports for now..."""
172
self.system_manager.port_manager.free_ports(self.port_block)
174
def set_server_options(self, server_options):
175
""" We update our server_options to the new set """
176
self.server_options = server_options
179
""" Voodoo to reset ourselves """
181
self.need_reset = False
183
def get_numeric_server_id(self):
184
""" Return the integer value of server-id
185
Mainly for mysql / percona, but may be useful elsewhere
189
return int(self.name.split(self.server_manager.server_base_name)[1])
191
def start(self, working_environ=None, expect_fail=0):
192
""" Start an individual server and return
193
an error code if it did not start in a timely manner
195
Start the server, using the options in option_list
196
as well as self.standard_options
198
if expect_fail = 1, we know the server shouldn't
202
# set pid to None for a new start
204
# get our current working environment
205
if not working_environ:
206
working_environ = self.test_executor.working_environment
207
# take care of any environment updates we need to do
208
self.server_manager.handle_environment_reqs(self, working_environ)
210
self.logging.verbose("Starting server: %s.%s" %(self.owner, self.name))
211
start_cmd = self.get_start_cmd()
212
self.logging.debug("Starting server with:")
213
self.logging.debug("%s" %(start_cmd))
214
# we signal we tried to start as an attempt
215
# to catch the case where a server is just
216
# starting up and the user ctrl-c's
217
# we don't know the server is running (still starting up)
218
# so we give it a few
219
#self.tried_start = 1
220
error_log = open(self.error_log,'w')
221
if start_cmd: # It will be none if --manual-gdb used
222
if not self.server_manager.gdb:
223
server_subproc = subprocess.Popen( start_cmd
225
, env=working_environ
229
server_subproc.wait()
230
server_retcode = server_subproc.returncode
232
# This is a bit hackish - need to see if there is a cleaner
233
# way of handling this
234
# It is annoying that we have to say stdout + stderr = None
235
# We might need to further manipulate things so that we
237
server_subproc = subprocess.Popen( start_cmd
239
, env = working_environ
252
timeout = float(self.server_start_timeout)
254
#if server_retcode: # We know we have an error, no need to wait
256
while not self.is_started() and timer != timeout:
257
time.sleep(self.timer_increment)
258
# If manual-gdb, this == None and we want to give the
259
# user all the time they need
261
timer= timer + self.timer_increment
263
if timer == timeout and not self.ping(quiet=True):
264
self.logging.error(( "Server failed to start within %d seconds. This could be a problem with the test machine or the server itself" %(timeout)))
267
if server_retcode == 0:
268
self.status = 1 # we are running
269
if os.path.exists(self.pid_file):
270
with open(self.pid_file,'r') as pid_file:
271
pid = pid_file.readline().strip()
275
if server_retcode != 0 and not expect_fail:
276
self.logging.error("Server startup command: %s failed with error code %d" %( start_cmd
278
self.logging.error("Dumping error log: %s" %(self.error_log))
279
with open(self.error_log,'r') as errlog:
281
self.logging.error(line.strip())
282
elif server_retcode == 0 and expect_fail:
283
# catch a startup that should have failed and report
284
self.logging.error("Server startup command :%s expected to fail, but succeeded" %(start_cmd))
287
if self.need_to_set_master:
288
# TODO handle a bad slave retcode
289
slave_retcode = self.set_master(self.master)
290
return server_retcode ^ expect_fail
292
def ping(self, quiet=False):
293
""" Ping / check if the server is alive
294
Return True if server is up and running, False otherwise
297
ping_cmd = self.get_ping_cmd()
299
self.logging.info("Pinging %s server on port %d" % (self.type.upper(), self.master_port))
300
(retcode, output)= self.system_manager.execute_cmd(ping_cmd, must_pass = 0)
305
""" Stop an individual server if it is running """
307
# we expect that we issued the command to start
308
# the server but it isn't up and running
309
# we kill a bit of time waiting for it
311
while not self.ping(quiet=True) and attempts_remain:
313
attempts_remain = attempts_remain - 1
314
# Now we try to shut the server down
315
if self.ping(quiet=True):
316
self.logging.verbose("Stopping server %s.%s" %(self.owner, self.name))
317
stop_cmd = self.get_stop_cmd()
318
self.logging.debug("with shutdown command:\n %s" %(stop_cmd))
319
#retcode, output = self.system_manager.execute_cmd(stop_cmd)
320
shutdown_subproc = subprocess.Popen( stop_cmd
323
shutdown_subproc.wait()
324
shutdown_retcode = shutdown_subproc.returncode
325
# We do some monitoring for the server PID and kill it
326
# if need be. This is a bit of a band-aid for the
327
# zombie-server bug on Natty : ( Need to find the cause.
328
attempts_remain = 100
329
while self.system_manager.find_pid(self.pid) and attempts_remain:
331
attempts_remain = attempts_remain - 1
332
if not attempts_remain: # we kill the pid
334
self.logging.warning("Forcing kill of server pid: %s" %(server.pid))
335
self.system_manager.kill_pid(self.pid)
337
self.logging.error("Problem shutting down server:")
338
self.logging.error("%s" %(shutdown_retcode))
341
self.status = 0 # indicate we are shutdown
343
# make sure the server is indicated as stopped
347
""" This causes us to kill the server pid """
348
self.system_manager.kill_pid(self.get_pid())
351
""" We check our pid file and get what is there """
352
if os.path.exists(self.pid_file):
353
with open(self.pid_file,'r') as pid_file:
354
pid = pid_file.readline().strip()
359
def get_engine_info(self):
360
""" Check innodb / xtradb version """
362
innodb_version = None
363
xtradb_version = None
364
#if not self.code_tree.version_checked:
365
query = "SHOW VARIABLES LIKE 'innodb_version'"
366
retcode, result = execute_query(query, self)
367
# result format = (('innodb_version', '1.1.6-20.1'),)
369
innodb_version = result[0][1]
370
split_data = innodb_version.split('-')
371
if len(split_data) > 1:
372
xtradb_version = split_data[-1]
373
self.code_tree.version_checked = True
374
self.code_tree.innodb_version = innodb_version
375
self.code_tree.xtradb_version = xtradb_version
376
return innodb_version, xtradb_version
378
def dump_errlog(self):
379
with open(self.error_log,'r') as errlog:
380
data = errlog.readlines()