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
21
"""server_management.py
22
code for dealing with apportioning servers
23
to suit the needs of the tests and executors
31
from ConfigParser import RawConfigParser
34
""" code that handles the server objects
35
We use this to track, do mass actions, etc
36
Single point of contact for this business
40
def __init__(self, system_manager, variables):
41
self.skip_keys = [ 'system_manager'
47
self.debug = variables['debug']
48
self.verbose = variables['verbose']
50
# we try this to shorten things - will see how this works
51
self.server_base_name = 's'
52
self.no_secure_file_priv = variables['nosecurefilepriv']
53
self.system_manager = system_manager
54
self.code_manager = system_manager.code_manager
55
self.env_manager = system_manager.env_manager
56
self.logging = system_manager.logging
57
self.gdb = self.system_manager.gdb
58
self.default_storage_engine = variables['defaultengine']
59
self.default_server_type = variables['defaultservertype']
60
self.user_server_opts = variables['drizzledoptions']
63
self.libeatmydata = variables['libeatmydata']
64
self.libeatmydata_path = variables['libeatmydatapath']
66
self.logging.info("Using default-storage-engine: %s" %(self.default_storage_engine))
67
test_server = self.allocate_server( 'test_bot'
70
, self.system_manager.workdir
72
self.logging.info("Testing for Innodb / Xtradb version...")
73
test_server.start(working_environ=os.environ)
75
innodb_ver, xtradb_ver = test_server.get_engine_info()
76
self.logging.info("Innodb version: %s" %innodb_ver)
77
self.logging.info("Xtradb version: %s" %xtradb_ver)
79
self.logging.error("Problem detecting innodb/xtradb version:")
80
self.logging.error(Exception)
82
self.logging.error("Dumping server error.log...")
83
test_server.dump_errlog()
86
shutil.rmtree(test_server.workdir)
89
self.logging.debug_class(self)
91
def request_servers( self
99
""" We produce the server objects / start the server processes
100
as requested. We report errors and whatnot if we can't
101
That is, unless we expect the server to not start, then
102
we just return a value / message.
104
server_requirements is a list of lists. Each list
105
is a set of server options - we create one server
106
for each set of options requested
110
# Make sure our server is in a decent state, if the last test
111
# failed, then we reset the server
112
self.check_server_status(requester)
114
# Make sure we have the proper number of servers for this requester
115
self.process_server_count( requester
117
, len(server_requirements)
119
, server_requirements)
121
# Make sure we are running with the correct options
122
self.evaluate_existing_servers( requester
125
, server_requirements)
127
# Fire our servers up
128
bad_start = self.start_servers( requester
131
# Return them to the requester
132
return (self.get_server_list(requester), bad_start)
136
def allocate_server( self
142
, server_version=None):
143
""" Intialize an appropriate server object.
144
Start up occurs elsewhere
147
# use default server type unless specifically requested to do otherwise
149
server_type = self.default_server_type
151
# Get a name for our server
152
server_name = self.get_server_name(requester)
154
# initialize our new server_object
155
# get the right codeTree type from the code manager
156
code_tree = self.code_manager.get_tree(server_type, server_version)
158
# import the correct server type object
159
if server_type == 'drizzle':
160
from lib.server_mgmt.drizzled import drizzleServer as server_type
161
elif server_type == 'mysql':
162
from lib.server_mgmt.mysqld import mysqlServer as server_type
163
elif server_type == 'galera':
164
from lib.server_mgmt.galera import mysqlServer as server_type
166
new_server = server_type( server_name
169
, self.default_storage_engine
176
def start_servers(self, requester, expect_fail):
177
""" Start all servers for the requester """
180
for server in self.get_server_list(requester):
181
if server.status == 0:
182
bad_start = bad_start + server.start()
184
self.logging.debug("Server %s already running" %(server.name))
187
def stop_servers(self, requester):
188
""" Stop all servers running for the requester """
189
for server in self.get_server_list(requester):
192
def stop_server_list(self, server_list, free_ports=False):
193
""" Stop the servers in an arbitrary list of them """
194
for server in server_list:
199
def stop_all_servers(self):
200
""" Stop all running servers """
202
self.logging.info("Stopping all running servers...")
203
for server_list in self.servers.values():
204
for server in server_list:
207
def cleanup_all_servers(self):
208
"""Mainly for freeing server ports for now """
209
for server_list in self.servers.values():
210
for server in server_list:
214
"""Stop all servers and free their ports and whatnot """
215
self.stop_all_servers()
216
self.cleanup_all_servers()
218
def get_server_name(self, requester):
219
""" We name our servers requester.server_basename.count
220
where count is on a per-requester basis
221
We see how many servers this requester has and name things
225
self.has_servers(requester) # if requester isn't there, we create a blank entry
226
server_count = self.server_count(requester)
227
return "%s%d" %(self.server_base_name, server_count)
229
def has_servers(self, requester):
230
""" Check if the given requester has any servers """
231
if requester not in self.servers: # new requester
232
self.log_requester(requester)
233
return self.server_count(requester)
235
def log_requester(self, requester):
236
""" We create a log entry for the new requester """
238
self.servers[requester] = []
240
def log_server(self, new_server, requester):
241
self.servers[requester].append(new_server)
243
def evaluate_existing_servers( self, requester, cnf_path
244
, server_requests, server_requirements):
245
""" See if the requester has any servers and if they
246
are suitable for the current test
248
We should have the proper number of servers at this point
252
# A dictionary that holds various tricks
253
# we can do with our test servers
254
special_processing_reqs = {}
256
# we have a direct dictionary in the testcase
257
# that asks for what we want and we use it
258
special_processing_reqs = server_requests
260
current_servers = self.servers[requester]
262
for index,server in enumerate(current_servers):
263
# We handle a reset in case we need it:
264
if server.need_reset:
265
self.reset_server(server)
266
server.need_reset = False
268
desired_server_options = server_requirements[index]
270
# do any special config processing - this can alter
271
# how we view our servers
273
self.handle_server_config_file( cnf_path
275
, special_processing_reqs
276
, desired_server_options
279
if self.compare_options( server.server_options
280
, desired_server_options):
283
# We need to reset what is running and change the server
285
desired_server_options = self.filter_server_options(desired_server_options)
286
self.reset_server(server)
287
self.update_server_options(server, desired_server_options)
288
self.handle_special_server_requests(special_processing_reqs, current_servers)
290
def handle_server_config_file( self
293
, special_processing_reqs
294
, desired_server_options
296
# We have a config reader so we can do
297
# special per-server magic for setting up more
298
# complex scenario-based testing (eg we use a certain datadir)
299
config_reader = RawConfigParser()
300
config_reader.read(cnf_path)
302
# Do our checking for config-specific madness we need to do
303
if config_reader and config_reader.has_section(server.name):
304
# mark server for restart in case it hasn't yet
305
# this method is a bit hackish - need better method later
306
if '--restart' not in desired_server_options:
307
desired_server_options.append('--restart')
308
# We handle various scenarios
309
server_config_data = config_reader.items(server.name)
310
for cnf_option, data in server_config_data:
311
if cnf_option == 'load-datadir':
313
request_key = 'datadir_requests'
314
if request_key not in special_processing_reqs:
315
special_processing_reqs[request_key] = []
316
special_processing_reqs[request_key].append((datadir_path,server))
318
def handle_special_server_requests(self, request_dictionary, current_servers):
319
""" We run through our set of special requests and do
320
the appropriate voodoo
323
for key, item in request_dictionary.items():
324
if key == 'datadir_requests':
325
self.load_datadirs(item, current_servers)
326
if key == 'join_cluster':
327
self.join_clusters(item, current_servers)
329
def filter_server_options(self, server_options):
330
""" Remove a list of options we don't want passed to the server
331
these are test-case specific options.
333
NOTE: It is a bad hack to allow test-runner commands
334
to mix with server options willy-nilly in master-opt files
335
as we do. We need to kill this at some point : (
338
remove_options = [ '--restart'
339
, '--skip-stack-trace'
343
for remove_option in remove_options:
344
if remove_option in server_options:
345
server_options.remove(remove_option)
346
return server_options
349
def compare_options(self, optlist1, optlist2):
350
""" Compare two sets of server options and see if they match """
351
return sorted(optlist1) == sorted(optlist2)
353
def reset_server(self, server):
355
server.restore_snapshot()
358
def reset_servers(self, requester):
359
for server in self.servers[requester]:
360
self.reset_server(server)
362
def load_datadirs(self, datadir_requests, current_servers):
363
""" We load source_dir to the server's datadir """
364
for source_dir, server in datadir_requests:
365
self.load_datadir(source_dir, server, current_servers)
367
def load_datadir(self, source_dir, server, current_servers):
368
""" We load source_dir to the server's datadir """
370
if type(server) == int:
371
# we have just an index (as we use in unittest files)
372
# and we get the server from current_servers[idx]
373
server = current_servers[server]
374
source_dir_path = os.path.join(server.vardir,'std_data_ln',source_dir)
375
self.system_manager.remove_dir(server.datadir)
376
self.system_manager.copy_dir(source_dir_path, server.datadir)
377
# We need to signal that the server will need to be reset as we're
378
# using a non-standard datadir
379
server.need_reset = True
381
def join_clusters(self, cluster_requests, current_servers):
382
""" We get a list of master, slave tuples and join
386
for cluster_set in cluster_requests:
387
self.join_node_to_cluster(cluster_set, current_servers)
390
def join_node_to_cluster(self, node_set, current_servers):
391
""" We join node_set[1] to node_set[0].
392
The server object is responsible for
393
implementing the voodoo required to
398
master = current_servers[node_set[0]]
399
slave = current_servers[node_set[1]]
400
slave.set_master(master)
401
# Assuming we'll reset master and slave for now...
402
master.need_reset = True
403
slave.need_reset = True
405
def process_server_count( self
411
""" We see how many servers we have. We shrink / grow
412
the requesters set of servers as needed.
414
If we shrink, we shutdown / reset the discarded servers
418
if desired_count < 0: desired_count = 1
420
current_count = self.has_servers(requester)
421
if desired_count > current_count:
422
for i in range(desired_count - current_count):
423
# We pass an empty options list when allocating
424
# We'll update the options to what is needed elsewhere
425
self.allocate_server(requester, test_executor, [], workdir)
426
elif desired_count < current_count:
427
good_servers = self.get_server_list(requester)[:desired_count]
428
retired_servers = self.get_server_list(requester)[desired_count - current_count:]
429
self.stop_server_list(retired_servers, free_ports=True)
430
self.set_server_list(requester, good_servers)
434
def server_count(self, requester):
435
""" Return how many servers the the requester has """
436
return len(self.servers[requester])
438
def get_server_list(self, requester):
439
""" Return the list of servers assigned to the requester """
440
self.has_servers(requester) # initialize, hacky : (
441
return self.servers[requester]
443
def set_server_list(self, requester, server_list):
444
""" Set the requesters list of servers to server_list """
446
self.servers[requester] = server_list
448
def add_server(self, requester, new_server):
449
""" Add new_server to the requester's set of servers """
450
self.servers[requester].append(new_server)
452
def update_server_options(self, server, server_options):
453
""" Change the option_list a server has to use on startup """
454
self.logging.debug("Updating server: %s options" %(server.name))
455
self.logging.debug("FROM: %s" %(server.server_options))
456
self.logging.debug("TO: %s" %(server_options))
457
server.set_server_options(server_options)
459
def get_server_count(self):
460
""" Find out how many servers we have out """
462
for server_list in self.servers.values():
463
for server in server_list:
464
server_count = server_count + 1
467
def check_server_status(self, requester):
468
""" Make sure our servers are good,
472
for server in self.get_server_list(requester):
473
if server.failed_test:
474
self.reset_server(server)
476
def handle_environment_reqs(self, server, working_environ):
477
""" We update the working_environ as we need to
478
before starting the server.
480
This includes things like libeatmydata, ld_preloads, etc
483
environment_reqs = {}
485
if self.libeatmydata:
486
# We want to use libeatmydata to disable fsyncs
487
# this speeds up test execution, but we only want
488
# it to happen for the servers' environments
490
environment_reqs.update({'LD_PRELOAD':self.libeatmydata_path})
493
ld_lib_paths = self.env_manager.join_env_var_values(server.code_tree.ld_lib_paths)
494
environment_reqs.update({'LD_LIBRARY_PATH' : self.env_manager.append_env_var( 'LD_LIBRARY_PATH'
499
, 'DYLD_LIBRARY_PATH' : self.env_manager.append_env_var( 'DYLD_LIBRARY_PATH'
506
self.env_manager.update_environment_vars(environment_reqs)