зеркало из https://github.com/github/vitess-gh.git
881 строка
30 KiB
Python
881 строка
30 KiB
Python
# Copyright 2017 Google Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Manage VTTablet during test."""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import time
|
|
import urllib2
|
|
import warnings
|
|
|
|
import MySQLdb
|
|
|
|
import environment
|
|
from mysql_flavor import mysql_flavor
|
|
from protocols_flavor import protocols_flavor
|
|
from topo_flavor.server import topo_server
|
|
from urlparse import urlparse
|
|
import utils
|
|
|
|
import grpc
|
|
from vtproto.tabletmanagerservice_pb2_grpc import TabletManagerStub
|
|
|
|
# Dropping a table inexplicably produces a warning despite
|
|
# the 'IF EXISTS' clause. Squelch these warnings.
|
|
warnings.simplefilter('ignore')
|
|
|
|
tablet_cell_map = {
|
|
62344: 'nj',
|
|
62044: 'nj',
|
|
41983: 'nj',
|
|
31981: 'ny',
|
|
}
|
|
|
|
|
|
def get_backup_storage_flags():
|
|
return ['-backup_storage_implementation', 'file',
|
|
'-file_backup_storage_root',
|
|
os.path.join(environment.tmproot, 'backupstorage')]
|
|
|
|
|
|
def get_all_extra_my_cnf(extra_my_cnf):
|
|
all_extra_my_cnf = [environment.vttop + '/config/mycnf/default-fast.cnf']
|
|
flavor_my_cnf = mysql_flavor().extra_my_cnf()
|
|
if flavor_my_cnf:
|
|
all_extra_my_cnf.append(flavor_my_cnf)
|
|
if extra_my_cnf:
|
|
all_extra_my_cnf.append(extra_my_cnf)
|
|
return all_extra_my_cnf
|
|
|
|
|
|
class Tablet(object):
|
|
"""This class helps manage a vttablet instance.
|
|
|
|
To use it for vttablet, you need to use init_tablet and/or
|
|
start_vttablet.
|
|
"""
|
|
default_uid = 62344
|
|
seq = 0
|
|
tablets_running = 0
|
|
|
|
def __init__(self, tablet_uid=None, port=None, mysql_port=None, cell=None,
|
|
use_mysqlctld=False, vt_dba_passwd=None):
|
|
self.tablet_uid = tablet_uid or (Tablet.default_uid + Tablet.seq)
|
|
self.port = port or (environment.reserve_ports(1))
|
|
self.mysql_port = mysql_port or (environment.reserve_ports(1))
|
|
self.grpc_port = environment.reserve_ports(1)
|
|
self.use_mysqlctld = use_mysqlctld
|
|
self.vt_dba_passwd = vt_dba_passwd
|
|
Tablet.seq += 1
|
|
|
|
if cell:
|
|
self.cell = cell
|
|
else:
|
|
self.cell = tablet_cell_map.get(tablet_uid, 'nj')
|
|
self.proc = None
|
|
|
|
# filled in during init_mysql
|
|
self.mysqlctld_process = None
|
|
|
|
# filled in during init_tablet
|
|
self.keyspace = None
|
|
self.shard = None
|
|
self.index = None
|
|
self.tablet_index = None
|
|
# default to false
|
|
self.external_mysql = False
|
|
# utility variables
|
|
self.tablet_alias = 'test_%s-%010d' % (self.cell, self.tablet_uid)
|
|
self.zk_tablet_path = (
|
|
'/zk/test_%s/vt/tablets/%010d' % (self.cell, self.tablet_uid))
|
|
|
|
def __str__(self):
|
|
return 'tablet: uid: %d web: http://localhost:%d/ rpc port: %d' % (
|
|
self.tablet_uid, self.port, self.grpc_port)
|
|
|
|
def mysqlctl(self, cmd, extra_my_cnf=None, with_ports=False, verbose=False,
|
|
extra_args=None):
|
|
"""Runs a mysqlctl command.
|
|
|
|
Args:
|
|
cmd: the command to run.
|
|
extra_my_cnf: list of extra mycnf files to use
|
|
with_ports: if set, sends the tablet and mysql ports to mysqlctl.
|
|
verbose: passed to mysqlctld.
|
|
extra_args: passed to mysqlctl.
|
|
Returns:
|
|
the result of run_bg.
|
|
"""
|
|
extra_env = {}
|
|
all_extra_my_cnf = get_all_extra_my_cnf(extra_my_cnf)
|
|
if all_extra_my_cnf:
|
|
extra_env['EXTRA_MY_CNF'] = ':'.join(all_extra_my_cnf)
|
|
args = environment.binary_args('mysqlctl') + [
|
|
'-log_dir', environment.vtlogroot,
|
|
'-tablet_uid', str(self.tablet_uid)]
|
|
if self.use_mysqlctld:
|
|
args.extend(
|
|
['-mysqlctl_socket', os.path.join(self.tablet_dir, 'mysqlctl.sock')])
|
|
if with_ports:
|
|
args.extend(['-port', str(self.port),
|
|
'-mysql_port', str(self.mysql_port)])
|
|
if verbose:
|
|
args.append('-alsologtostderr')
|
|
if extra_args:
|
|
args.extend(extra_args)
|
|
args.extend(cmd)
|
|
return utils.run_bg(args, extra_env=extra_env)
|
|
|
|
def mysqlctld(self, cmd, extra_my_cnf=None, verbose=False, extra_args=None):
|
|
"""Runs a mysqlctld command.
|
|
|
|
Args:
|
|
cmd: the command to run.
|
|
extra_my_cnf: list of extra mycnf files to use
|
|
verbose: passed to mysqlctld.
|
|
extra_args: passed to mysqlctld.
|
|
Returns:
|
|
the result of run_bg.
|
|
"""
|
|
extra_env = {}
|
|
all_extra_my_cnf = get_all_extra_my_cnf(extra_my_cnf)
|
|
if all_extra_my_cnf:
|
|
extra_env['EXTRA_MY_CNF'] = ':'.join(all_extra_my_cnf)
|
|
args = environment.binary_args('mysqlctld') + [
|
|
'-log_dir', environment.vtlogroot,
|
|
'-tablet_uid', str(self.tablet_uid),
|
|
'-mysql_port', str(self.mysql_port),
|
|
'-socket_file', os.path.join(self.tablet_dir, 'mysqlctl.sock')]
|
|
if verbose:
|
|
args.append('-alsologtostderr')
|
|
if extra_args:
|
|
args.extend(extra_args)
|
|
args.extend(cmd)
|
|
return utils.run_bg(args, extra_env=extra_env)
|
|
|
|
def init_mysql(self, extra_my_cnf=None, init_db=None, extra_args=None,
|
|
use_rbr=False):
|
|
"""Init the mysql tablet directory, starts mysqld.
|
|
|
|
Either runs 'mysqlctl init', or starts a mysqlctld process.
|
|
|
|
Args:
|
|
extra_my_cnf: to pass to mysqlctl.
|
|
init_db: if set, use this init_db script instead of the default.
|
|
extra_args: passed to mysqlctld / mysqlctl.
|
|
use_rbr: configure the MySQL daemon to use RBR.
|
|
|
|
Returns:
|
|
The forked process.
|
|
"""
|
|
if use_rbr:
|
|
if extra_my_cnf:
|
|
extra_my_cnf += ':' + environment.vttop + '/config/mycnf/rbr.cnf'
|
|
else:
|
|
extra_my_cnf = environment.vttop + '/config/mycnf/rbr.cnf'
|
|
|
|
if not init_db:
|
|
init_db = environment.vttop + '/config/init_db.sql'
|
|
|
|
if self.use_mysqlctld:
|
|
self.mysqlctld_process = self.mysqlctld(['-init_db_sql_file', init_db],
|
|
extra_my_cnf=extra_my_cnf,
|
|
extra_args=extra_args)
|
|
return self.mysqlctld_process
|
|
else:
|
|
return self.mysqlctl(['init', '-init_db_sql_file', init_db],
|
|
extra_my_cnf=extra_my_cnf, with_ports=True,
|
|
extra_args=extra_args)
|
|
|
|
def start_mysql(self):
|
|
return self.mysqlctl(['start'], with_ports=True)
|
|
|
|
def shutdown_mysql(self, extra_args=None):
|
|
return self.mysqlctl(['shutdown'], with_ports=True, extra_args=extra_args)
|
|
|
|
def teardown_mysql(self, extra_args=None):
|
|
if self.use_mysqlctld and self.mysqlctld_process:
|
|
# if we use mysqlctld, we just terminate it gracefully, so it kills
|
|
# its mysqld. And we return it, so we can wait for it.
|
|
utils.kill_sub_process(self.mysqlctld_process, soft=True)
|
|
return self.mysqlctld_process
|
|
if utils.options.keep_logs:
|
|
return self.shutdown_mysql(extra_args=extra_args)
|
|
return self.mysqlctl(['teardown', '-force'], extra_args=extra_args)
|
|
|
|
def remove_tree(self, ignore_options=False):
|
|
if not ignore_options and utils.options.keep_logs:
|
|
return
|
|
try:
|
|
shutil.rmtree(self.tablet_dir)
|
|
except OSError as e:
|
|
if utils.options.verbose == 2:
|
|
print >> sys.stderr, e, self.tablet_dir
|
|
|
|
def mysql_connection_parameters(self, dbname, user='vt_dba'):
|
|
result = dict(user=user,
|
|
db=dbname)
|
|
if user == 'vt_dba' and self.vt_dba_passwd:
|
|
result['passwd'] = self.vt_dba_passwd
|
|
return result
|
|
|
|
def connect(self, dbname='', user='vt_dba', **params):
|
|
params.update(self.mysql_connection_parameters(dbname, user))
|
|
if 'port' not in params.keys():
|
|
params['unix_socket']=self.tablet_dir + '/mysql.sock'
|
|
else:
|
|
params['host']='127.0.0.1'
|
|
conn = MySQLdb.Connect(**params)
|
|
return conn, conn.cursor()
|
|
|
|
# Query the MySQL instance directly
|
|
def mquery(self, dbname, query, write=False, user='vt_dba', conn_params=None,
|
|
log_query=False):
|
|
"""Runs a query to MySQL directly, using python's SQL driver.
|
|
|
|
Args:
|
|
dbname: if set, the dbname to use.
|
|
query: the SQL query (or queries) to run.
|
|
write: if set, wraps the query in a transaction.
|
|
user: the db user to use.
|
|
conn_params: extra mysql connection parameters.
|
|
log_query: if true, the query will be logged.
|
|
Returns:
|
|
the rows.
|
|
"""
|
|
if conn_params is None:
|
|
conn_params = {}
|
|
if self.external_mysql:
|
|
conn_params['port']=self.mysql_port
|
|
conn, cursor = self.connect(dbname, user=user, **conn_params)
|
|
if write:
|
|
conn.begin()
|
|
if isinstance(query, basestring):
|
|
query = [query]
|
|
|
|
for q in query:
|
|
if log_query:
|
|
logging.debug('mysql(%s,%s): %s', self.tablet_uid, dbname, q)
|
|
cursor.execute(q)
|
|
|
|
if write:
|
|
conn.commit()
|
|
|
|
try:
|
|
return cursor.fetchall()
|
|
finally:
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
def assert_table_count(self, dbname, table, n, where=''):
|
|
result = self.mquery(dbname, 'select count(*) from ' + table + ' ' + where)
|
|
if result[0][0] != n:
|
|
raise utils.TestError('expected %d rows in %s' % (n, table), result)
|
|
|
|
def reset_replication(self):
|
|
self.mquery('', mysql_flavor().reset_replication_commands())
|
|
|
|
def set_semi_sync_enabled(self, master=None, slave=None):
|
|
logging.debug('mysql(%s): setting semi-sync mode: master=%s, slave=%s',
|
|
self.tablet_uid, master, slave)
|
|
self.mquery('',
|
|
mysql_flavor().set_semi_sync_enabled_commands(master, slave))
|
|
|
|
def populate(self, dbname, create_sql, insert_sqls=None):
|
|
self.create_db(dbname)
|
|
if isinstance(create_sql, basestring):
|
|
create_sql = [create_sql]
|
|
for q in create_sql:
|
|
self.mquery(dbname, q)
|
|
if insert_sqls:
|
|
for q in insert_sqls:
|
|
self.mquery(dbname, q, write=True)
|
|
|
|
def has_db(self, name):
|
|
rows = self.mquery('', 'show databases')
|
|
for row in rows:
|
|
dbname = row[0]
|
|
if dbname == name:
|
|
return True
|
|
return False
|
|
|
|
def drop_db(self, name):
|
|
self.mquery('', 'drop database if exists %s' % name)
|
|
while self.has_db(name):
|
|
logging.debug('%s sleeping while waiting for database drop: %s',
|
|
self.tablet_alias, name)
|
|
time.sleep(0.3)
|
|
self.mquery('', 'drop database if exists %s' % name)
|
|
|
|
def create_db(self, name):
|
|
self.drop_db(name)
|
|
self.mquery('', 'create database %s' % name)
|
|
|
|
def clean_dbs(self, include_vt=False):
|
|
logging.debug('mysql(%s): removing all databases', self.tablet_uid)
|
|
rows = self.mquery('', 'show databases')
|
|
for row in rows:
|
|
dbname = row[0]
|
|
if dbname in ['information_schema', 'performance_schema', 'mysql', 'sys']:
|
|
continue
|
|
if dbname == '_vt' and not include_vt:
|
|
continue
|
|
self.drop_db(dbname)
|
|
|
|
def wait_check_db_var(self, name, value):
|
|
for _ in range(3):
|
|
try:
|
|
return self.check_db_var(name, value)
|
|
except utils.TestError as e:
|
|
print >> sys.stderr, 'WARNING: ', e
|
|
time.sleep(1.0)
|
|
raise e
|
|
|
|
def check_db_var(self, name, value):
|
|
row = self.get_db_var(name)
|
|
if row != (name, value):
|
|
raise utils.TestError('variable not set correctly', name, row)
|
|
|
|
def get_db_var(self, name):
|
|
conn, cursor = self.connect()
|
|
try:
|
|
cursor.execute("show variables like '%s'" % name)
|
|
return cursor.fetchone()
|
|
finally:
|
|
conn.close()
|
|
|
|
def check_db_status(self, name, value):
|
|
row = self.get_db_status(name)
|
|
if row[1] != value:
|
|
raise utils.TestError('status not correct', name, row)
|
|
|
|
def get_db_status(self, name):
|
|
conn, cursor = self.connect()
|
|
try:
|
|
cursor.execute("show status like '%s'" % name)
|
|
return cursor.fetchone()
|
|
finally:
|
|
conn.close()
|
|
|
|
def update_addrs(self):
|
|
args = [
|
|
'UpdateTabletAddrs',
|
|
'-hostname', 'localhost',
|
|
'-ip-addr', '127.0.0.1',
|
|
'-mysql-port', '%d' % self.mysql_port,
|
|
'-vt-port', '%d' % self.port,
|
|
self.tablet_alias
|
|
]
|
|
return utils.run_vtctl(args)
|
|
|
|
def init_tablet(self, tablet_type, keyspace, shard,
|
|
tablet_index=None,
|
|
start=False, dbname=None, parent=True, wait_for_start=True,
|
|
include_mysql_port=True, external_mysql=False, **kwargs):
|
|
"""Initialize a tablet's record in topology."""
|
|
|
|
self.tablet_type = tablet_type
|
|
self.keyspace = keyspace
|
|
self.shard = shard
|
|
self.tablet_index = tablet_index
|
|
self.external_mysql = external_mysql
|
|
|
|
self.dbname = dbname or ('vt_' + self.keyspace)
|
|
|
|
args = ['InitTablet',
|
|
'-hostname', 'localhost',
|
|
'-port', str(self.port),
|
|
'-allow_update']
|
|
if include_mysql_port:
|
|
args.extend(['-mysql_port', str(self.mysql_port)])
|
|
if parent:
|
|
args.append('-parent')
|
|
if dbname:
|
|
args.extend(['-db_name_override', dbname])
|
|
if keyspace:
|
|
args.extend(['-keyspace', keyspace])
|
|
if shard:
|
|
args.extend(['-shard', shard])
|
|
args.extend([self.tablet_alias, tablet_type])
|
|
utils.run_vtctl(args)
|
|
if start:
|
|
if not wait_for_start:
|
|
expected_state = None
|
|
elif tablet_type == 'master':
|
|
expected_state = 'SERVING'
|
|
else:
|
|
expected_state = 'NOT_SERVING'
|
|
self.start_vttablet(wait_for_state=expected_state, **kwargs)
|
|
|
|
@property
|
|
def tablet_dir(self):
|
|
return '%s/vt_%010d' % (environment.vtdataroot, self.tablet_uid)
|
|
|
|
def grpc_enabled(self):
|
|
return (
|
|
protocols_flavor().tabletconn_protocol() == 'grpc' or
|
|
protocols_flavor().tablet_manager_protocol() == 'grpc' or
|
|
protocols_flavor().binlog_player_protocol() == 'grpc')
|
|
|
|
def semi_sync_enabled(self):
|
|
return self.enable_semi_sync
|
|
|
|
def flush(self):
|
|
utils.curl('http://localhost:%s%s' %
|
|
(self.port, environment.flush_logs_url),
|
|
stderr=utils.devnull, stdout=utils.devnull)
|
|
|
|
def start_vttablet(
|
|
self, port=None,
|
|
wait_for_state='SERVING', topocustomrule_path=None,
|
|
schema_override=None,
|
|
repl_extra_flags=None, table_acl_config=None,
|
|
lameduck_period=None, security_policy=None,
|
|
full_mycnf_args=False,
|
|
extra_args=None, extra_env=None, include_mysql_port=True,
|
|
init_tablet_type=None, init_keyspace=None, init_shard=None,
|
|
# TODO(mberlin): Assign the index automatically and remove this parameter.
|
|
tablet_index=None,
|
|
init_db_name_override=None,
|
|
binlog_use_v3_resharding_mode=True,
|
|
supports_backups=True, grace_period='1s', enable_semi_sync=True):
|
|
# pylint: disable=g-doc-args
|
|
"""Starts a vttablet process, and returns it.
|
|
|
|
The process is also saved in self.proc, so it's easy to kill as well.
|
|
|
|
Returns:
|
|
the process that was started.
|
|
"""
|
|
# pylint: enable=g-doc-args
|
|
|
|
args = environment.binary_args('vttablet')
|
|
# Use 'localhost' as hostname because Travis CI worker hostnames
|
|
# are too long for MySQL replication.
|
|
args.extend(['-tablet_hostname', 'localhost'])
|
|
args.extend(['-tablet-path', self.tablet_alias])
|
|
args.extend(environment.topo_server().flags())
|
|
args.extend(['-binlog_player_protocol',
|
|
protocols_flavor().binlog_player_protocol()])
|
|
args.extend(['-tablet_manager_protocol',
|
|
protocols_flavor().tablet_manager_protocol()])
|
|
args.extend(['-tablet_protocol', protocols_flavor().tabletconn_protocol()])
|
|
args.extend(['-vreplication_healthcheck_topology_refresh', '1s'])
|
|
args.extend(['-vreplication_healthcheck_retry_delay', '1s'])
|
|
args.extend(['-vreplication_retry_delay', '1s'])
|
|
args.extend(['-pid_file', os.path.join(self.tablet_dir, 'vttablet.pid')])
|
|
# always enable_replication_reporter with somewhat short values for tests
|
|
args.extend(['-health_check_interval', '2s'])
|
|
args.extend(['-enable_replication_reporter'])
|
|
args.extend(['-degraded_threshold', '5s'])
|
|
args.extend(['-lock_tables_timeout', '5s'])
|
|
args.extend(['-watch_replication_stream'])
|
|
if enable_semi_sync:
|
|
args.append('-enable_semi_sync')
|
|
# Remember the setting in case a test wants to know it.
|
|
self.enable_semi_sync = enable_semi_sync
|
|
if self.use_mysqlctld:
|
|
args.extend(
|
|
['-mysqlctl_socket', os.path.join(self.tablet_dir, 'mysqlctl.sock')])
|
|
|
|
if self.external_mysql:
|
|
args.extend(['-db_host', '127.0.0.1'])
|
|
args.extend(['-db_port', str(self.mysql_port)])
|
|
args.append('-disable_active_reparents')
|
|
if full_mycnf_args:
|
|
# this flag is used to specify all the mycnf_ flags, to make
|
|
# sure that code works.
|
|
relay_log_path = os.path.join(self.tablet_dir, 'relay-logs',
|
|
'vt-%010d-relay-bin' % self.tablet_uid)
|
|
args.extend([
|
|
'-mycnf_server_id', str(self.tablet_uid),
|
|
'-mycnf_data_dir', os.path.join(self.tablet_dir, 'data'),
|
|
'-mycnf_innodb_data_home_dir', os.path.join(self.tablet_dir,
|
|
'innodb', 'data'),
|
|
'-mycnf_innodb_log_group_home_dir', os.path.join(self.tablet_dir,
|
|
'innodb', 'logs'),
|
|
'-mycnf_socket_file', os.path.join(self.tablet_dir, 'mysql.sock'),
|
|
'-mycnf_error_log_path', os.path.join(self.tablet_dir, 'error.log'),
|
|
'-mycnf_slow_log_path', os.path.join(self.tablet_dir,
|
|
'slow-query.log'),
|
|
'-mycnf_relay_log_path', relay_log_path,
|
|
'-mycnf_relay_log_index_path', relay_log_path + '.index',
|
|
'-mycnf_relay_log_info_path', os.path.join(self.tablet_dir,
|
|
'relay-logs',
|
|
'relay-log.info'),
|
|
'-mycnf_bin_log_path', os.path.join(
|
|
self.tablet_dir, 'bin-logs', 'vt-%010d-bin' % self.tablet_uid),
|
|
'-mycnf_master_info_file', os.path.join(self.tablet_dir,
|
|
'master.info'),
|
|
'-mycnf_pid_file', os.path.join(self.tablet_dir, 'mysql.pid'),
|
|
'-mycnf_tmp_dir', os.path.join(self.tablet_dir, 'tmp'),
|
|
'-mycnf_slave_load_tmp_dir', os.path.join(self.tablet_dir, 'tmp'),
|
|
])
|
|
if include_mysql_port:
|
|
args.extend(['-mycnf_mysql_port', str(self.mysql_port)])
|
|
|
|
# this is used to run InitTablet as part of the vttablet startup
|
|
if init_tablet_type:
|
|
self.tablet_type = init_tablet_type
|
|
args.extend(['-init_tablet_type', init_tablet_type])
|
|
if init_keyspace:
|
|
self.keyspace = init_keyspace
|
|
self.shard = init_shard
|
|
# tablet_index is required for the update_addr call below.
|
|
if self.tablet_index is None:
|
|
self.tablet_index = tablet_index
|
|
args.extend(['-init_keyspace', init_keyspace,
|
|
'-init_shard', init_shard])
|
|
if init_db_name_override:
|
|
self.dbname = init_db_name_override
|
|
args.extend(['-init_db_name_override', init_db_name_override])
|
|
else:
|
|
self.dbname = 'vt_' + init_keyspace
|
|
|
|
# Default value for this flag is True. So, add it only if it's false.
|
|
if not binlog_use_v3_resharding_mode:
|
|
args.extend(['-binlog_use_v3_resharding_mode=false'])
|
|
|
|
if supports_backups:
|
|
args.extend(['-restore_from_backup'] + get_backup_storage_flags())
|
|
|
|
# When vttablet restores from backup, it will re-generate the .cnf file.
|
|
# So we need to have EXTRA_MY_CNF set properly.
|
|
# When using mysqlctld, only mysqlctld should need EXTRA_MY_CNF.
|
|
# If any test fails without giving EXTRA_MY_CNF to vttablet,
|
|
# it means we missed some call that should run remotely on mysqlctld.
|
|
if not self.use_mysqlctld:
|
|
all_extra_my_cnf = get_all_extra_my_cnf(None)
|
|
if all_extra_my_cnf:
|
|
if not extra_env:
|
|
extra_env = {}
|
|
extra_env['EXTRA_MY_CNF'] = ':'.join(all_extra_my_cnf)
|
|
|
|
if extra_args:
|
|
args.extend(extra_args)
|
|
|
|
args.extend(['-port', '%s' % (port or self.port),
|
|
'-log_dir', environment.vtlogroot])
|
|
|
|
if topocustomrule_path:
|
|
args.extend(['-topocustomrule_path', topocustomrule_path])
|
|
|
|
if schema_override:
|
|
args.extend(['-schema-override', schema_override])
|
|
|
|
if table_acl_config:
|
|
args.extend(['-table-acl-config', table_acl_config])
|
|
args.extend(['-queryserver-config-strict-table-acl'])
|
|
|
|
if protocols_flavor().service_map():
|
|
args.extend(['-service_map', ','.join(protocols_flavor().service_map())])
|
|
if self.grpc_enabled():
|
|
args.extend(['-grpc_port', str(self.grpc_port)])
|
|
args.extend(['-grpc_max_message_size',
|
|
str(environment.grpc_max_message_size)])
|
|
if lameduck_period:
|
|
args.extend(environment.lameduck_flag(lameduck_period))
|
|
if grace_period:
|
|
args.extend(['-serving_state_grace_period', grace_period])
|
|
if security_policy:
|
|
args.extend(['-security_policy', security_policy])
|
|
|
|
args.extend(['-enable-autocommit'])
|
|
stderr_fd = open(
|
|
os.path.join(environment.vtlogroot, 'vttablet-%d.stderr' %
|
|
self.tablet_uid), 'w')
|
|
# increment count only the first time
|
|
if not self.proc:
|
|
Tablet.tablets_running += 1
|
|
self.proc = utils.run_bg(args, stderr=stderr_fd, extra_env=extra_env)
|
|
|
|
log_message = (
|
|
'Started vttablet: %s (%s) with pid: %s - Log files: '
|
|
'%s/vttablet.*.{INFO,WARNING,ERROR,FATAL}.*.%s' %
|
|
(self.tablet_uid, self.tablet_alias, self.proc.pid,
|
|
environment.vtlogroot, self.proc.pid))
|
|
# This may race with the stderr output from the process (though
|
|
# that's usually empty).
|
|
stderr_fd.write(log_message + '\n')
|
|
stderr_fd.close()
|
|
logging.debug(log_message)
|
|
|
|
# wait for query service to be in the right state
|
|
if wait_for_state:
|
|
self.wait_for_vttablet_state(wait_for_state, port=port)
|
|
|
|
if self.tablet_index is not None:
|
|
topo_server().update_addr(
|
|
'test_'+self.cell, self.keyspace, self.shard,
|
|
self.tablet_index, (port or self.port))
|
|
|
|
return self.proc
|
|
|
|
def wait_for_vttablet_state(self, expected, timeout=60.0, port=None):
|
|
expr = re.compile('^' + expected + '$')
|
|
while True:
|
|
v = utils.get_vars(port or self.port)
|
|
last_seen_state = '?'
|
|
if v is None:
|
|
if self.proc.poll() is not None:
|
|
raise utils.TestError(
|
|
'vttablet died while test waiting for state %s' % expected)
|
|
logging.debug(
|
|
' vttablet %s not answering at /debug/vars, waiting...',
|
|
self.tablet_alias)
|
|
else:
|
|
if 'TabletStateName' not in v:
|
|
logging.debug(
|
|
' vttablet %s not exporting TabletStateName, waiting...',
|
|
self.tablet_alias)
|
|
else:
|
|
s = v['TabletStateName']
|
|
last_seen_state = s
|
|
if expr.match(s):
|
|
break
|
|
else:
|
|
logging.debug(
|
|
' vttablet %s in state: %s, expected: %s', self.tablet_alias, s,
|
|
expected)
|
|
timeout = utils.wait_step(
|
|
'%s state %s (last seen state: %s)' %
|
|
(self.tablet_alias, expected, last_seen_state),
|
|
timeout, sleep_time=0.1)
|
|
|
|
def wait_for_mysqlctl_socket(self, timeout=60.0):
|
|
mysql_sock = os.path.join(self.tablet_dir, 'mysql.sock')
|
|
mysqlctl_sock = os.path.join(self.tablet_dir, 'mysqlctl.sock')
|
|
while True:
|
|
wait_for = []
|
|
if not os.path.exists(mysql_sock):
|
|
wait_for.append(mysql_sock)
|
|
if not os.path.exists(mysqlctl_sock):
|
|
wait_for.append(mysqlctl_sock)
|
|
if not wait_for:
|
|
return
|
|
timeout = utils.wait_step('waiting for socket files: %s' % str(wait_for),
|
|
timeout, sleep_time=2.0)
|
|
|
|
def get_status(self):
|
|
return utils.get_status(self.port)
|
|
|
|
def get_healthz(self):
|
|
return urllib2.urlopen('http://localhost:%d/healthz' % self.port).read()
|
|
|
|
def kill_vttablet(self, wait=True):
|
|
"""Sends a SIG_TERM to the tablet.
|
|
|
|
Args:
|
|
wait: will wait for the process to exit.
|
|
Returns:
|
|
the subprocess object (use it to get exit code).
|
|
"""
|
|
logging.debug('killing vttablet: %s, wait: %s', self.tablet_alias,
|
|
str(wait))
|
|
proc = self.proc
|
|
if proc is not None:
|
|
Tablet.tablets_running -= 1
|
|
if proc.poll() is None:
|
|
proc.terminate()
|
|
if wait:
|
|
proc.wait()
|
|
self.proc = None
|
|
return proc
|
|
|
|
def hard_kill_vttablet(self):
|
|
logging.debug('hard killing vttablet: %s', self.tablet_alias)
|
|
if self.proc is not None:
|
|
Tablet.tablets_running -= 1
|
|
if self.proc.poll() is None:
|
|
self.proc.kill()
|
|
self.proc.wait()
|
|
self.proc = None
|
|
|
|
def wait_for_binlog_server_state(self, expected, timeout=30.0):
|
|
"""Wait for the tablet's binlog server to be in the provided state.
|
|
|
|
Args:
|
|
expected: the state to wait for.
|
|
timeout: how long to wait before error.
|
|
"""
|
|
while True:
|
|
v = utils.get_vars(self.port)
|
|
if v is None:
|
|
if self.proc.poll() is not None:
|
|
raise utils.TestError(
|
|
'vttablet died while test waiting for binlog state %s' %
|
|
expected)
|
|
logging.debug(' vttablet not answering at /debug/vars, waiting...')
|
|
else:
|
|
if 'UpdateStreamState' not in v:
|
|
logging.debug(
|
|
' vttablet not exporting BinlogServerState, waiting...')
|
|
else:
|
|
s = v['UpdateStreamState']
|
|
if s != expected:
|
|
logging.debug(" vttablet's binlog server in state %s != %s", s,
|
|
expected)
|
|
else:
|
|
break
|
|
timeout = utils.wait_step(
|
|
'waiting for binlog server state %s' % expected,
|
|
timeout, sleep_time=0.5)
|
|
logging.debug('tablet %s binlog service is in state %s',
|
|
self.tablet_alias, expected)
|
|
|
|
def wait_for_binlog_player_count(self, expected, timeout=30.0):
|
|
"""Wait for a tablet to have binlog players.
|
|
|
|
Args:
|
|
expected: number of expected binlog players to wait for.
|
|
timeout: how long to wait.
|
|
"""
|
|
while True:
|
|
v = utils.get_vars(self.port)
|
|
if v is None:
|
|
if self.proc.poll() is not None:
|
|
raise utils.TestError(
|
|
'vttablet died while test waiting for binlog count %s' %
|
|
expected)
|
|
logging.debug(' vttablet not answering at /debug/vars, waiting...')
|
|
else:
|
|
if 'VReplicationStreamCount' not in v:
|
|
logging.debug(
|
|
' vttablet not exporting VReplicationStreamCount, waiting...')
|
|
else:
|
|
s = v['VReplicationStreamCount']
|
|
if s != expected:
|
|
logging.debug(" vttablet's binlog player map has count %d != %d",
|
|
s, expected)
|
|
else:
|
|
break
|
|
timeout = utils.wait_step(
|
|
'waiting for binlog player count %d' % expected,
|
|
timeout, sleep_time=0.5)
|
|
logging.debug('tablet %s binlog player has %d players',
|
|
self.tablet_alias, expected)
|
|
|
|
@classmethod
|
|
def check_vttablet_count(cls):
|
|
if Tablet.tablets_running > 0:
|
|
raise utils.TestError('This test is not killing all its vttablets')
|
|
|
|
def execute(self, sql, bindvars=None, transaction_id=None,
|
|
execute_options=None, auto_log=True):
|
|
"""execute uses 'vtctl VtTabletExecute' to execute a command.
|
|
|
|
Args:
|
|
sql: the command to execute.
|
|
bindvars: a dict of bind variables.
|
|
transaction_id: the id of the transaction to use if necessary.
|
|
execute_options: proto-encoded ExecuteOptions object.
|
|
auto_log: passed to run_vtctl.
|
|
|
|
Returns:
|
|
the result of running vtctl command.
|
|
"""
|
|
args = [
|
|
'VtTabletExecute', '-json',
|
|
]
|
|
if bindvars:
|
|
args.extend(['-bind_variables', json.dumps(bindvars)])
|
|
if transaction_id:
|
|
args.extend(['-transaction_id', str(transaction_id)])
|
|
if execute_options:
|
|
args.extend(['-options', execute_options])
|
|
args.extend([self.tablet_alias, sql])
|
|
return utils.run_vtctl_json(args, auto_log=auto_log)
|
|
|
|
def begin(self, auto_log=True):
|
|
"""begin uses 'vtctl VtTabletBegin' to start a transaction.
|
|
|
|
Args:
|
|
auto_log: passed to run_vtctl.
|
|
|
|
Returns:
|
|
the transaction id.
|
|
"""
|
|
args = [
|
|
'VtTabletBegin',
|
|
self.tablet_alias,
|
|
]
|
|
result = utils.run_vtctl_json(args, auto_log=auto_log)
|
|
return result['transaction_id']
|
|
|
|
def commit(self, transaction_id, auto_log=True):
|
|
"""commit uses 'vtctl VtTabletCommit' to commit a transaction.
|
|
|
|
Args:
|
|
transaction_id: id of the transaction to roll back.
|
|
auto_log: passed to run_vtctl.
|
|
|
|
Returns:
|
|
the return code for run_vtctl.
|
|
"""
|
|
args = [
|
|
'VtTabletCommit',
|
|
self.tablet_alias,
|
|
str(transaction_id),
|
|
]
|
|
return utils.run_vtctl(args, auto_log=auto_log)
|
|
|
|
def rollback(self, transaction_id, auto_log=True):
|
|
"""rollback uses 'vtctl VtTabletRollback' to rollback a transaction.
|
|
|
|
Args:
|
|
transaction_id: id of the transaction to roll back.
|
|
auto_log: passed to run_vtctl.
|
|
|
|
Returns:
|
|
the return code for run_vtctl.
|
|
"""
|
|
args = [
|
|
'VtTabletRollback',
|
|
self.tablet_alias,
|
|
str(transaction_id),
|
|
]
|
|
return utils.run_vtctl(args, auto_log=auto_log)
|
|
|
|
def rpc_endpoint(self):
|
|
"""Returns the protocol and endpoint to use for RPCs."""
|
|
if self.grpc_enabled():
|
|
return 'localhost:%d' % self.grpc_port
|
|
return 'localhost:%d' % self.port
|
|
|
|
def tablet_manager(self):
|
|
"""Returns a rpc client able to talk to the TabletManager rpc server in go"""
|
|
addr = self.rpc_endpoint()
|
|
p = urlparse('http://' + addr)
|
|
channel = grpc.insecure_channel('%s:%s' % (p.hostname, p.port))
|
|
return TabletManagerStub(channel)
|
|
|
|
def kill_tablets(tablets):
|
|
for t in tablets:
|
|
logging.debug('killing vttablet: %s', t.tablet_alias)
|
|
if t.proc is not None:
|
|
Tablet.tablets_running -= 1
|
|
t.proc.terminate()
|
|
|
|
for t in tablets:
|
|
if t.proc is not None:
|
|
t.proc.wait()
|
|
t.proc = None
|
|
|
|
|