зеркало из https://github.com/github/vitess-gh.git
1508 строки
57 KiB
Python
Executable File
1508 строки
57 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# coding: utf-8
|
|
|
|
import logging
|
|
from multiprocessing.pool import ThreadPool
|
|
import pprint
|
|
import struct
|
|
import threading
|
|
import time
|
|
import traceback
|
|
import unittest
|
|
|
|
import environment
|
|
import tablet
|
|
import utils
|
|
from protocols_flavor import protocols_flavor
|
|
|
|
from vtdb import dbexceptions
|
|
from vtdb import keyrange
|
|
from vtdb import keyrange_constants
|
|
from vtdb import vtdb_logger
|
|
from vtdb import vtgate_client
|
|
from vtdb import vtgate_cursor
|
|
|
|
shard_0_master = tablet.Tablet()
|
|
shard_0_replica1 = tablet.Tablet()
|
|
shard_0_replica2 = tablet.Tablet()
|
|
|
|
shard_1_master = tablet.Tablet()
|
|
shard_1_replica1 = tablet.Tablet()
|
|
shard_1_replica2 = tablet.Tablet()
|
|
|
|
KEYSPACE_NAME = 'test_keyspace'
|
|
SHARD_NAMES = ['-80', '80-']
|
|
SHARD_KID_MAP = {
|
|
'-80': [
|
|
527875958493693904, 626750931627689502,
|
|
345387386794260318, 332484755310826578,
|
|
1842642426274125671, 1326307661227634652,
|
|
1761124146422844620, 1661669973250483744,
|
|
3361397649937244239, 2444880764308344533],
|
|
'80-': [
|
|
9767889778372766922, 9742070682920810358,
|
|
10296850775085416642, 9537430901666854108,
|
|
10440455099304929791, 11454183276974683945,
|
|
11185910247776122031, 10460396697869122981,
|
|
13379616110062597001, 12826553979133932576],
|
|
}
|
|
|
|
CREATE_VT_INSERT_TEST = '''create table vt_insert_test (
|
|
id bigint auto_increment,
|
|
msg varchar(64),
|
|
keyspace_id bigint(20) unsigned NOT NULL,
|
|
primary key (id)
|
|
) Engine=InnoDB'''
|
|
|
|
CREATE_VT_A = '''create table vt_a (
|
|
eid bigint,
|
|
id int,
|
|
keyspace_id bigint(20) unsigned NOT NULL,
|
|
primary key(eid, id)
|
|
) Engine=InnoDB'''
|
|
|
|
CREATE_VT_FIELD_TYPES = '''create table vt_field_types (
|
|
id bigint(20) auto_increment,
|
|
uint_val bigint(20) unsigned,
|
|
str_val varchar(64),
|
|
unicode_val varchar(64),
|
|
float_val float(5, 1),
|
|
keyspace_id bigint(20) unsigned NOT NULL,
|
|
primary key(id)
|
|
) Engine=InnoDB'''
|
|
|
|
|
|
create_tables = [CREATE_VT_INSERT_TEST, CREATE_VT_A, CREATE_VT_FIELD_TYPES]
|
|
pack_kid = struct.Struct('!Q').pack
|
|
|
|
|
|
class DBRow(object):
|
|
|
|
def __init__(self, column_names, row_tuple):
|
|
self.__dict__ = dict(zip(column_names, row_tuple))
|
|
|
|
def __repr__(self):
|
|
return pprint.pformat(self.__dict__, 4)
|
|
|
|
|
|
def setUpModule():
|
|
logging.debug('in setUpModule')
|
|
try:
|
|
environment.topo_server().setup()
|
|
|
|
# start mysql instance external to the test
|
|
setup_procs = [shard_0_master.init_mysql(),
|
|
shard_0_replica1.init_mysql(),
|
|
shard_0_replica2.init_mysql(),
|
|
shard_1_master.init_mysql(),
|
|
shard_1_replica1.init_mysql(),
|
|
shard_1_replica2.init_mysql()
|
|
]
|
|
utils.wait_procs(setup_procs)
|
|
setup_tablets()
|
|
except:
|
|
tearDownModule()
|
|
raise
|
|
|
|
|
|
def tearDownModule():
|
|
logging.debug('in tearDownModule')
|
|
if utils.options.skip_teardown:
|
|
return
|
|
logging.debug('Tearing down the servers and setup')
|
|
tablet.kill_tablets([shard_0_master,
|
|
shard_0_replica1, shard_0_replica2,
|
|
shard_1_master,
|
|
shard_1_replica1, shard_1_replica2])
|
|
teardown_procs = [shard_0_master.teardown_mysql(),
|
|
shard_0_replica1.teardown_mysql(),
|
|
shard_0_replica2.teardown_mysql(),
|
|
shard_1_master.teardown_mysql(),
|
|
shard_1_replica1.teardown_mysql(),
|
|
shard_1_replica2.teardown_mysql(),
|
|
]
|
|
utils.wait_procs(teardown_procs, raise_on_error=False)
|
|
|
|
environment.topo_server().teardown()
|
|
|
|
utils.kill_sub_processes()
|
|
utils.remove_tmp_files()
|
|
|
|
shard_0_master.remove_tree()
|
|
shard_0_replica1.remove_tree()
|
|
shard_0_replica2.remove_tree()
|
|
shard_1_master.remove_tree()
|
|
shard_1_replica1.remove_tree()
|
|
shard_1_replica2.remove_tree()
|
|
|
|
|
|
def setup_tablets():
|
|
# Start up a master mysql and vttablet
|
|
logging.debug('Setting up tablets')
|
|
utils.run_vtctl(['CreateKeyspace', KEYSPACE_NAME])
|
|
utils.run_vtctl(['SetKeyspaceShardingInfo', '-force', KEYSPACE_NAME,
|
|
'keyspace_id', 'uint64'])
|
|
shard_0_master.init_tablet('master', keyspace=KEYSPACE_NAME, shard='-80')
|
|
shard_0_replica1.init_tablet('replica', keyspace=KEYSPACE_NAME, shard='-80')
|
|
shard_0_replica2.init_tablet('replica', keyspace=KEYSPACE_NAME, shard='-80')
|
|
shard_1_master.init_tablet('master', keyspace=KEYSPACE_NAME, shard='80-')
|
|
shard_1_replica1.init_tablet('replica', keyspace=KEYSPACE_NAME, shard='80-')
|
|
shard_1_replica2.init_tablet('replica', keyspace=KEYSPACE_NAME, shard='80-')
|
|
|
|
utils.run_vtctl(['RebuildKeyspaceGraph', KEYSPACE_NAME], auto_log=True)
|
|
|
|
for t in [shard_0_master, shard_0_replica1, shard_0_replica2,
|
|
shard_1_master, shard_1_replica1, shard_1_replica2]:
|
|
t.create_db('vt_test_keyspace')
|
|
for create_table in create_tables:
|
|
t.mquery(shard_0_master.dbname, create_table)
|
|
t.start_vttablet(wait_for_state=None)
|
|
|
|
for t in [shard_0_master, shard_0_replica1, shard_0_replica2,
|
|
shard_1_master, shard_1_replica1, shard_1_replica2]:
|
|
t.wait_for_vttablet_state('SERVING')
|
|
|
|
utils.run_vtctl(['InitShardMaster', KEYSPACE_NAME+'/-80',
|
|
shard_0_master.tablet_alias], auto_log=True)
|
|
utils.run_vtctl(['InitShardMaster', KEYSPACE_NAME+'/80-',
|
|
shard_1_master.tablet_alias], auto_log=True)
|
|
|
|
utils.run_vtctl(
|
|
['RebuildKeyspaceGraph', KEYSPACE_NAME], auto_log=True)
|
|
|
|
utils.check_srv_keyspace(
|
|
'test_nj', KEYSPACE_NAME,
|
|
'Partitions(master): -80 80-\n'
|
|
'Partitions(rdonly): -80 80-\n'
|
|
'Partitions(replica): -80 80-\n')
|
|
|
|
utils.VtGate().start()
|
|
|
|
|
|
def get_connection(timeout=10.0):
|
|
protocol = protocols_flavor().vtgate_python_protocol()
|
|
try:
|
|
return vtgate_client.connect(protocol, utils.vtgate.addr(), timeout)
|
|
except Exception:
|
|
logging.exception('Connection to vtgate (timeout=%s) failed.', timeout)
|
|
raise
|
|
|
|
|
|
def get_keyrange(shard_name):
|
|
kr = None
|
|
if shard_name == keyrange_constants.SHARD_ZERO:
|
|
kr = keyrange.KeyRange(keyrange_constants.NON_PARTIAL_KEYRANGE)
|
|
else:
|
|
kr = keyrange.KeyRange(shard_name)
|
|
return kr
|
|
|
|
|
|
def _delete_all(shard_index, table_name):
|
|
vtgate_conn = get_connection()
|
|
# This write is to set up the test with fresh insert
|
|
# and hence performing it directly on the connection.
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute('delete from %s' % table_name, {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[get_keyrange(SHARD_NAMES[shard_index])])
|
|
vtgate_conn.commit()
|
|
|
|
|
|
def write_rows_to_shard(count, shard_index):
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[shard_index]]
|
|
_delete_all(shard_index, 'vt_insert_test')
|
|
vtgate_conn = get_connection()
|
|
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x % len(kid_list)]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_insert_test (msg, keyspace_id) '
|
|
'values (%(msg)s, %(keyspace_id)s)',
|
|
{'msg': 'test %s' % x, 'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
|
|
|
|
def restart_vtgate(extra_args=None):
|
|
if extra_args is None:
|
|
extra_args = {}
|
|
port = utils.vtgate.port
|
|
utils.vtgate.kill()
|
|
utils.VtGate(port=port).start(extra_args=extra_args)
|
|
|
|
|
|
class BaseTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(BaseTestCase, self).setUp()
|
|
logging.info('Start: %s.', '.'.join(self.id().split('.')[-2:]))
|
|
|
|
|
|
class TestCoreVTGateFunctions(BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestCoreVTGateFunctions, self).setUp()
|
|
self.shard_index = 1
|
|
self.keyrange = get_keyrange(SHARD_NAMES[self.shard_index])
|
|
self.master_tablet = shard_1_master
|
|
self.replica_tablet = shard_1_replica1
|
|
|
|
def test_status(self):
|
|
self.assertIn('</html>', utils.vtgate.get_status())
|
|
|
|
def test_connect(self):
|
|
vtgate_conn = get_connection()
|
|
self.assertNotEqual(vtgate_conn, None)
|
|
|
|
def test_writes(self):
|
|
vtgate_conn = get_connection()
|
|
_delete_all(self.shard_index, 'vt_insert_test')
|
|
count = 10
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[count%len(kid_list)]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_insert_test (msg, keyspace_id) '
|
|
'values (%(msg)s, %(keyspace_id)s)',
|
|
{'msg': 'test %s' % x, 'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
rowcount = cursor.execute('select * from vt_insert_test', {})
|
|
self.assertEqual(rowcount, count, 'master fetch works')
|
|
|
|
def test_query_routing(self):
|
|
"""Test VtGate routes queries to the right tablets."""
|
|
row_counts = [20, 30]
|
|
for shard_index in [0, 1]:
|
|
write_rows_to_shard(row_counts[shard_index], shard_index)
|
|
vtgate_conn = get_connection()
|
|
for shard_index in [0, 1]:
|
|
# Fetch all rows in each shard
|
|
cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[get_keyrange(SHARD_NAMES[shard_index])])
|
|
rowcount = cursor.execute('select * from vt_insert_test', {})
|
|
# Verify row count
|
|
self.assertEqual(rowcount, row_counts[shard_index])
|
|
# Verify keyspace id
|
|
for result in cursor.results:
|
|
kid = result[2]
|
|
self.assertIn(kid, SHARD_KID_MAP[SHARD_NAMES[shard_index]])
|
|
# Do a cross shard range query and assert all rows are fetched
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyranges=[get_keyrange('75-95')])
|
|
rowcount = cursor.execute('select * from vt_insert_test', {})
|
|
self.assertEqual(rowcount, row_counts[0] + row_counts[1])
|
|
|
|
def test_rollback(self):
|
|
vtgate_conn = get_connection()
|
|
count = 10
|
|
_delete_all(self.shard_index, 'vt_insert_test')
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_insert_test (msg, keyspace_id) '
|
|
'values (%(msg)s, %(keyspace_id)s)',
|
|
{'msg': 'test %s' % x, 'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.rollback()
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
rowcount = cursor.execute('select * from vt_insert_test', {})
|
|
logging.debug('ROLLBACK TEST rowcount %d count %d', rowcount, count)
|
|
self.assertEqual(
|
|
rowcount, count,
|
|
"Fetched rows(%d) != inserted rows(%d), rollback didn't work" %
|
|
(rowcount, count))
|
|
write_rows_to_shard(10, self.shard_index)
|
|
|
|
def test_execute_entity_ids(self):
|
|
vtgate_conn = get_connection()
|
|
count = 10
|
|
_delete_all(self.shard_index, 'vt_a')
|
|
eid_map = {}
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
eid_map[x] = pack_kid(keyspace_id)
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_a (eid, id, keyspace_id) '
|
|
'values (%(eid)s, %(id)s, %(keyspace_id)s)',
|
|
{'eid': x, 'id': x, 'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master', keyspace_ids=None)
|
|
rowcount = cursor.execute(
|
|
'select * from vt_a', {},
|
|
entity_keyspace_id_map=eid_map, entity_column_name='id')
|
|
self.assertEqual(rowcount, count, 'entity_ids works')
|
|
|
|
def test_batch_read(self):
|
|
vtgate_conn = get_connection()
|
|
count = 10
|
|
_delete_all(self.shard_index, 'vt_insert_test')
|
|
shard_name = SHARD_NAMES[self.shard_index]
|
|
kid_list = SHARD_KID_MAP[shard_name]
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_insert_test (msg, keyspace_id) '
|
|
'values (%(msg)s, %(keyspace_id)s)',
|
|
{'msg': 'test %s' % x, 'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
_delete_all(self.shard_index, 'vt_a')
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_a (eid, id, keyspace_id) '
|
|
'values (%(eid)s, %(id)s, %(keyspace_id)s)',
|
|
{'eid': x, 'id': x, 'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
kid_list = [pack_kid(kid) for kid in kid_list]
|
|
cursor = vtgate_conn.cursor(keyspace=None, tablet_type='master')
|
|
# Test ExecuteBatchKeyspaceIds
|
|
params_list = [
|
|
dict(sql='select msg, keyspace_id from vt_insert_test',
|
|
bind_variables={},
|
|
keyspace=KEYSPACE_NAME, keyspace_ids=kid_list,
|
|
shards=None),
|
|
dict(sql='select eid, id, keyspace_id from vt_a',
|
|
bind_variables={},
|
|
keyspace=KEYSPACE_NAME,
|
|
keyspace_ids=None,
|
|
shards=[shard_name]),
|
|
dict(sql='select eid + 100, id, keyspace_id from vt_a',
|
|
bind_variables={},
|
|
keyspace=KEYSPACE_NAME, keyspace_ids=kid_list,
|
|
shards=None),
|
|
]
|
|
cursor.executemany(sql=None, params_list=params_list)
|
|
self.assertEqual(cursor.rowcount, count)
|
|
msg_0, msg_1 = (row[0] for row in sorted(cursor.fetchall())[:2])
|
|
self.assertEqual(msg_0, 'test 0')
|
|
self.assertEqual(msg_1, 'test 1')
|
|
self.assertTrue(cursor.nextset())
|
|
self.assertEqual(cursor.rowcount, count)
|
|
eid_0, eid_1 = (row[0] for row in sorted(cursor.fetchall())[:2])
|
|
self.assertEqual(eid_0, 0)
|
|
self.assertEqual(eid_1, 1)
|
|
self.assertTrue(cursor.nextset())
|
|
eid_0_plus_100, eid_1_plus_100 = (
|
|
row[0] for row in sorted(cursor.fetchall())[:2])
|
|
self.assertEqual(eid_0_plus_100, 100)
|
|
self.assertEqual(eid_1_plus_100, 101)
|
|
self.assertFalse(cursor.nextset())
|
|
|
|
def test_batch_write(self):
|
|
vtgate_conn = get_connection()
|
|
cursor = vtgate_conn.cursor(keyspace=None, tablet_type='master')
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
all_ids = [pack_kid(kid) for kid in kid_list]
|
|
count = 10
|
|
cursor.executemany(
|
|
sql=None,
|
|
params_list=[
|
|
dict(sql='delete from vt_insert_test', bind_variables=None,
|
|
keyspace=KEYSPACE_NAME, keyspace_ids=all_ids,
|
|
shards=None)])
|
|
|
|
params_list = []
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
params_list.append(
|
|
dict(sql=None,
|
|
bind_variables=
|
|
{'msg': 'test %s' % x, 'keyspace_id': keyspace_id},
|
|
keyspace=KEYSPACE_NAME,
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
shards=None))
|
|
cursor.executemany(
|
|
sql='insert into vt_insert_test (msg, keyspace_id) '
|
|
'values (%(msg)s, %(keyspace_id)s)',
|
|
params_list=params_list)
|
|
cursor.executemany(
|
|
sql=None,
|
|
params_list=[
|
|
dict(sql='delete from vt_a', bind_variables=None,
|
|
keyspace=KEYSPACE_NAME, keyspace_ids=all_ids, shards=None)])
|
|
params_list = []
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
sql = (
|
|
'insert into vt_a (eid, id, keyspace_id) '
|
|
'values (%(eid)s, %(id)s, %(keyspace_id)s)')
|
|
bind_variables = {'eid': x, 'id': x, 'keyspace_id': keyspace_id}
|
|
keyspace = KEYSPACE_NAME
|
|
keyspace_ids = [pack_kid(keyspace_id)]
|
|
params_list.append(dict(
|
|
sql=sql, bind_variables=bind_variables, keyspace=keyspace,
|
|
keyspace_ids=keyspace_ids, shards=None))
|
|
cursor.executemany(sql=None, params_list=params_list)
|
|
_, rowcount, _, _ = vtgate_conn._execute(
|
|
'select * from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
self.assertEqual(rowcount, count)
|
|
_, rowcount, _, _ = vtgate_conn._execute(
|
|
'select * from vt_a', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
self.assertEqual(rowcount, count)
|
|
|
|
def test_streaming_fetchsubset(self):
|
|
count = 30
|
|
write_rows_to_shard(count, self.shard_index)
|
|
# Fetch a subset of the total size.
|
|
vtgate_conn = get_connection()
|
|
|
|
def get_stream_cursor():
|
|
return vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
|
|
def fetch_first_10_rows(stream_cursor):
|
|
stream_cursor.execute('select msg from vt_insert_test', {})
|
|
rows = stream_cursor.fetchmany(size=10)
|
|
self.assertEqual(rows, [('test %d' % x,) for x in xrange(10)])
|
|
|
|
def fetch_next_10_rows(stream_cursor):
|
|
rows = stream_cursor.fetchmany(size=10)
|
|
self.assertEqual(rows, [('test %d' % x,) for x in xrange(10, 20)])
|
|
|
|
# Open two streaming queries at the same time, fetch some from each,
|
|
# and make sure they don't interfere with each other.
|
|
stream_cursor_1 = get_stream_cursor()
|
|
stream_cursor_2 = get_stream_cursor()
|
|
fetch_first_10_rows(stream_cursor_1)
|
|
fetch_first_10_rows(stream_cursor_2)
|
|
fetch_next_10_rows(stream_cursor_1)
|
|
fetch_next_10_rows(stream_cursor_2)
|
|
stream_cursor_1.close()
|
|
stream_cursor_2.close()
|
|
|
|
def test_streaming_fetchall(self):
|
|
count = 30
|
|
write_rows_to_shard(count, self.shard_index)
|
|
# Fetch all.
|
|
vtgate_conn = get_connection()
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
rows = stream_cursor.fetchall()
|
|
rowcount = len(list(rows))
|
|
self.assertEqual(rowcount, count)
|
|
stream_cursor.close()
|
|
|
|
def test_streaming_fetchone(self):
|
|
count = 30
|
|
write_rows_to_shard(count, self.shard_index)
|
|
# Fetch one.
|
|
vtgate_conn = get_connection()
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
rows = stream_cursor.fetchone()
|
|
self.assertTrue(type(rows) == tuple, 'Received a valid row')
|
|
stream_cursor.close()
|
|
|
|
def test_streaming_multishards(self):
|
|
count = 30
|
|
write_rows_to_shard(count, 0)
|
|
write_rows_to_shard(count, 1)
|
|
vtgate_conn = get_connection()
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[keyrange.KeyRange(
|
|
keyrange_constants.NON_PARTIAL_KEYRANGE)],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
rows = stream_cursor.fetchall()
|
|
rowcount = len(list(rows))
|
|
self.assertEqual(rowcount, count * 2)
|
|
stream_cursor.close()
|
|
|
|
def test_streaming_zero_results(self):
|
|
vtgate_conn = get_connection()
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute('delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
# After deletion, should result zero.
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
rows = stream_cursor.fetchall()
|
|
rowcount = len(list(rows))
|
|
self.assertEqual(rowcount, 0)
|
|
|
|
def test_interleaving(self):
|
|
tablet_type = 'master'
|
|
try:
|
|
vtgate_conn = get_connection()
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, tablet_type,
|
|
keyranges=[self.keyrange])
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
count = len(kid_list)
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x]
|
|
vtgate_conn._execute(
|
|
'insert into vt_insert_test (msg, keyspace_id) '
|
|
'values (%(msg)s, %(keyspace_id)s)',
|
|
{'msg': 'test %s' % x, 'keyspace_id': keyspace_id},
|
|
KEYSPACE_NAME, tablet_type, keyspace_ids=[pack_kid(keyspace_id)])
|
|
vtgate_conn.commit()
|
|
vtgate_conn2 = get_connection()
|
|
query = (
|
|
'select keyspace_id from vt_insert_test where keyspace_id = %(kid)s')
|
|
thd = threading.Thread(target=self._query_lots, args=(
|
|
vtgate_conn2,
|
|
query,
|
|
{'kid': kid_list[0]},
|
|
KEYSPACE_NAME,
|
|
tablet_type,
|
|
[pack_kid(kid_list[0])]))
|
|
thd.start()
|
|
for i in xrange(count):
|
|
(result, _, _, _) = vtgate_conn._execute(
|
|
query,
|
|
{'kid': kid_list[i]},
|
|
KEYSPACE_NAME, tablet_type,
|
|
keyspace_ids=[pack_kid(kid_list[i])])
|
|
self.assertEqual(result, [(kid_list[i],)])
|
|
if i % 10 == 0:
|
|
generator, _ = vtgate_conn._stream_execute(
|
|
query, {'kid': kid_list[i]}, KEYSPACE_NAME,
|
|
tablet_type,
|
|
keyspace_ids=[pack_kid(kid_list[i])])
|
|
for result in generator:
|
|
self.assertEqual(result, (kid_list[i],))
|
|
thd.join()
|
|
except Exception, e:
|
|
self.fail('Failed with error %s %s' % (str(e), traceback.format_exc()))
|
|
|
|
def test_field_types(self):
|
|
vtgate_conn = get_connection()
|
|
_delete_all(self.shard_index, 'vt_field_types')
|
|
count = 10
|
|
base_uint = int('8' + '0' * 15, base=16)
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
for x in xrange(1, count):
|
|
keyspace_id = kid_list[count % len(kid_list)]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
cursor.begin()
|
|
cursor.execute(
|
|
'insert into vt_field_types '
|
|
'(uint_val, str_val, unicode_val, float_val, keyspace_id) '
|
|
'values (%(uint_val)s, %(str_val)s, %(unicode_val)s, '
|
|
'%(float_val)s, %(keyspace_id)s)',
|
|
{'uint_val': base_uint + x, 'str_val': 'str_%d' % x,
|
|
'unicode_val': unicode('str_%d' % x), 'float_val': x * 1.2,
|
|
'keyspace_id': keyspace_id})
|
|
cursor.commit()
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
rowcount = cursor.execute('select * from vt_field_types', {})
|
|
field_names = [f[0] for f in cursor.description]
|
|
self.assertEqual(rowcount, count -1, "rowcount doesn't match")
|
|
id_list = []
|
|
uint_val_list = []
|
|
str_val_list = []
|
|
unicode_val_list = []
|
|
float_val_list = []
|
|
for r in cursor.results:
|
|
row = DBRow(field_names, r)
|
|
id_list.append(row.id)
|
|
uint_val_list.append(row.uint_val)
|
|
str_val_list.append(row.str_val)
|
|
unicode_val_list.append(row.unicode_val)
|
|
float_val_list.append(row.float_val)
|
|
|
|
# iterable type checks - list, tuple, set are supported.
|
|
query = 'select * from vt_field_types where id in %(id_1)s'
|
|
rowcount = cursor.execute(query, {'id_1': id_list})
|
|
self.assertEqual(rowcount, len(id_list), "rowcount doesn't match")
|
|
rowcount = cursor.execute(query, {'id_1': tuple(id_list)})
|
|
self.assertEqual(rowcount, len(id_list), "rowcount doesn't match")
|
|
rowcount = cursor.execute(query, {'id_1': set(id_list)})
|
|
self.assertEqual(rowcount, len(id_list), "rowcount doesn't match")
|
|
for r in cursor.results:
|
|
row = DBRow(field_names, r)
|
|
self.assertIsInstance(row.id, (int, long))
|
|
|
|
# received field types same as input.
|
|
# uint
|
|
query = 'select * from vt_field_types where uint_val in %(uint_val_1)s'
|
|
rowcount = cursor.execute(query, {'uint_val_1': uint_val_list})
|
|
self.assertEqual(rowcount, len(uint_val_list), "rowcount doesn't match")
|
|
for _, r in enumerate(cursor.results):
|
|
row = DBRow(field_names, r)
|
|
self.assertIsInstance(row.uint_val, long)
|
|
self.assertGreaterEqual(
|
|
row.uint_val, base_uint, 'uint value not in correct range')
|
|
|
|
# str
|
|
query = 'select * from vt_field_types where str_val in %(str_val_1)s'
|
|
rowcount = cursor.execute(query, {'str_val_1': str_val_list})
|
|
self.assertEqual(rowcount, len(str_val_list), "rowcount doesn't match")
|
|
for r in cursor.results:
|
|
row = DBRow(field_names, r)
|
|
self.assertIsInstance(row.str_val, str)
|
|
|
|
# unicode str
|
|
query = (
|
|
'select * from vt_field_types where unicode_val in %(unicode_val_1)s')
|
|
rowcount = cursor.execute(query, {'unicode_val_1': unicode_val_list})
|
|
self.assertEqual(
|
|
rowcount, len(unicode_val_list), "rowcount doesn't match")
|
|
for r in cursor.results:
|
|
row = DBRow(field_names, r)
|
|
self.assertIsInstance(row.unicode_val, basestring)
|
|
|
|
# deliberately eliminating the float test since it is flaky due
|
|
# to mysql float precision handling.
|
|
|
|
def _query_lots(
|
|
self, conn, query, bind_vars, keyspace_name, tablet_type, keyspace_ids):
|
|
for _ in xrange(500):
|
|
result, _, _, _ = conn._execute(
|
|
query, bind_vars, keyspace_name, tablet_type,
|
|
keyspace_ids=keyspace_ids)
|
|
self.assertEqual(result, [tuple(bind_vars.values())])
|
|
|
|
|
|
class TestFailures(BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestFailures, self).setUp()
|
|
self.shard_index = 1
|
|
self.keyrange = get_keyrange(SHARD_NAMES[self.shard_index])
|
|
self.master_tablet = shard_1_master
|
|
self.master_tablet.kill_vttablet()
|
|
self.tablet_start(self.master_tablet, 'master')
|
|
self.replica_tablet = shard_1_replica1
|
|
self.replica_tablet.kill_vttablet()
|
|
self.tablet_start(self.replica_tablet, 'replica')
|
|
self.replica_tablet2 = shard_1_replica2
|
|
self.replica_tablet2.kill_vttablet()
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
port = utils.vtgate.port
|
|
utils.vtgate.kill()
|
|
utils.VtGate(port=port).start()
|
|
|
|
def tablet_start(self, tablet_obj, tablet_type, lameduck_period='0.5s'):
|
|
_ = tablet_type
|
|
return tablet_obj.start_vttablet(lameduck_period=lameduck_period)
|
|
|
|
def test_status_with_error(self):
|
|
"""Tests that the status page loads correctly after a VTGate error."""
|
|
vtgate_conn = get_connection()
|
|
cursor = vtgate_conn.cursor(
|
|
'INVALID_KEYSPACE', 'replica', keyspace_ids=['0'])
|
|
# We expect to see a DatabaseError due to an invalid keyspace
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
cursor.execute('select * from vt_insert_test', {})
|
|
# Page should have loaded successfully
|
|
self.assertIn('</html>', utils.vtgate.get_status())
|
|
|
|
def test_tablet_restart_read(self):
|
|
# Since we're going to kill the tablet, there will be a race between the
|
|
# client timeout here and the vtgate->vttablet connection timeout, so we
|
|
# increase it for this test.
|
|
vtgate_conn = get_connection(timeout=30)
|
|
self.replica_tablet.kill_vttablet()
|
|
self.replica_tablet2.kill_vttablet()
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.tablet_start(self.replica_tablet, 'replica')
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
try:
|
|
_ = vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
except Exception, e:
|
|
self.fail('Communication with shard %s replica failed with error %s' %
|
|
(SHARD_NAMES[self.shard_index], str(e)))
|
|
|
|
def test_vtgate_restart_read(self):
|
|
vtgate_conn = get_connection()
|
|
port = utils.vtgate.port
|
|
utils.vtgate.kill()
|
|
with self.assertRaises(dbexceptions.OperationalError):
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
utils.VtGate(port=port).start()
|
|
vtgate_conn = get_connection()
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
|
|
def test_tablet_restart_stream_execute(self):
|
|
# Since we're going to kill the tablet, there will be a race between the
|
|
# client timeout here and the vtgate->vttablet connection timeout, so we
|
|
# increase it for this test.
|
|
vtgate_conn = get_connection(timeout=30)
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
self.replica_tablet.kill_vttablet()
|
|
self.replica_tablet2.kill_vttablet()
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
self.tablet_start(self.replica_tablet, 'replica')
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
try:
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
except Exception, e:
|
|
self.fail('Communication with shard0 replica failed with error %s' %
|
|
str(e))
|
|
|
|
def test_vtgate_restart_stream_execute(self):
|
|
vtgate_conn = get_connection()
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
port = utils.vtgate.port
|
|
utils.vtgate.kill()
|
|
with self.assertRaises(dbexceptions.OperationalError):
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
utils.VtGate(port=port).start()
|
|
vtgate_conn = get_connection()
|
|
stream_cursor = vtgate_conn.cursor(
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange],
|
|
cursorclass=vtgate_cursor.StreamVTGateCursor)
|
|
try:
|
|
stream_cursor.execute('select * from vt_insert_test', {})
|
|
except Exception, e:
|
|
self.fail('Communication with shard0 replica failed with error %s' %
|
|
str(e))
|
|
|
|
# vtgate begin doesn't make any back-end connections to
|
|
# vttablet so the kill and restart shouldn't have any effect.
|
|
def test_tablet_restart_begin(self):
|
|
vtgate_conn = get_connection()
|
|
self.master_tablet.kill_vttablet()
|
|
vtgate_conn.begin()
|
|
_ = self.tablet_start(self.master_tablet, 'master')
|
|
vtgate_conn.begin()
|
|
# this succeeds only if retry_count > 0
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
|
|
def test_vtgate_restart_begin(self):
|
|
vtgate_conn = get_connection()
|
|
port = utils.vtgate.port
|
|
utils.vtgate.kill()
|
|
with self.assertRaises(dbexceptions.OperationalError):
|
|
vtgate_conn.begin()
|
|
utils.VtGate(port=port).start()
|
|
vtgate_conn = get_connection()
|
|
vtgate_conn.begin()
|
|
|
|
def test_tablet_fail_write(self):
|
|
# Since we're going to kill the tablet, there will be a race between the
|
|
# client timeout here and the vtgate->vttablet connection timeout, so we
|
|
# increase it for this test.
|
|
vtgate_conn = get_connection(timeout=30)
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
vtgate_conn.begin()
|
|
self.master_tablet.kill_vttablet()
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
_ = self.tablet_start(self.master_tablet, 'master')
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
|
|
def test_vttablet_errors_not_logged(self):
|
|
"""Verifies that errors from VtTablet aren't logged as such in VTGate.
|
|
|
|
Instead of making assertions by reading the log stream, we read a debug
|
|
vars that is incremented by VTGate whenever it chooses to log exceptions
|
|
to Infof instead of Errorf.
|
|
"""
|
|
vtgate_conn = get_connection()
|
|
keyspace_id = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]][0]
|
|
cursor = vtgate_conn.cursor(KEYSPACE_NAME, 'master',
|
|
keyspace_ids=[pack_kid(keyspace_id)],
|
|
writable=True)
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
cursor.execute('this is not valid syntax, throw an error', {})
|
|
|
|
try:
|
|
non_vtgate_errors = (
|
|
utils.vtgate.get_vars()['VtgateInfoErrorCounts']['NonVtgateErrors'])
|
|
except KeyError:
|
|
self.fail(
|
|
"No errors in VTGate that weren't logged as exceptions: "
|
|
"'NonVtgateErrors' vars not found")
|
|
self.assertEqual(non_vtgate_errors, 1)
|
|
|
|
def test_error_on_dml(self):
|
|
vtgate_conn = get_connection()
|
|
vtgate_conn.begin()
|
|
keyspace_id = SHARD_KID_MAP[SHARD_NAMES[
|
|
(self.shard_index+1)%len(SHARD_NAMES)
|
|
]][0]
|
|
try:
|
|
vtgate_conn._execute(
|
|
'insert into vt_insert_test values(:msg, :keyspace_id)',
|
|
{'msg': 'test4', 'keyspace_id': keyspace_id}, KEYSPACE_NAME,
|
|
'master', keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
self.fail('Failed to raise DatabaseError exception')
|
|
except dbexceptions.DatabaseError:
|
|
# FIXME(alainjobart) add a method to get the session to vtgate_client,
|
|
# instead of poking into it like this.
|
|
if protocols_flavor().vtgate_python_protocol() == 'gorpc':
|
|
logging.info(
|
|
'SHARD SESSIONS: %s', vtgate_conn.session['ShardSessions'])
|
|
transaction_id = (
|
|
vtgate_conn.session['ShardSessions'][0]['TransactionId'])
|
|
else:
|
|
transaction_id = vtgate_conn.session.shard_sessions[0].transaction_id
|
|
self.assertTrue(transaction_id != 0)
|
|
except Exception, e:
|
|
self.fail('Expected DatabaseError as exception, got %s' % str(e))
|
|
finally:
|
|
vtgate_conn.rollback()
|
|
|
|
def test_vtgate_fail_write(self):
|
|
vtgate_conn = get_connection()
|
|
port = utils.vtgate.port
|
|
with self.assertRaises(dbexceptions.OperationalError):
|
|
vtgate_conn.begin()
|
|
utils.vtgate.kill()
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
utils.VtGate(port=port).start()
|
|
vtgate_conn = get_connection()
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'delete from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
|
|
# test timeout between py client and vtgate
|
|
def test_vtgate_timeout(self):
|
|
vtgate_conn = get_connection(timeout=3.0)
|
|
with self.assertRaises(dbexceptions.TimeoutError):
|
|
vtgate_conn._execute(
|
|
'select sleep(4) from dual', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
|
|
vtgate_conn = get_connection(timeout=3.0)
|
|
with self.assertRaises(dbexceptions.TimeoutError):
|
|
vtgate_conn._execute(
|
|
'select sleep(4) from dual', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
# Currently this is causing vttablet to become unreachable at
|
|
# the timeout boundary and kill any query being executed
|
|
# at the time. Prevent flakiness in other tests by sleeping
|
|
# until the query times out.
|
|
# TODO fix b/17733518
|
|
time.sleep(3)
|
|
|
|
# test timeout between vtgate and vttablet
|
|
# the timeout is set to 5 seconds
|
|
def test_tablet_timeout(self):
|
|
# this test only makes sense if there is a shorter/protective timeout
|
|
# set for vtgate-vttablet connection.
|
|
# TODO(liguo): evaluate if we want such a timeout
|
|
return
|
|
vtgate_conn = get_connection()
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'select sleep(7) from dual', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
|
|
vtgate_conn = get_connection()
|
|
with self.assertRaises(dbexceptions.DatabaseError):
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'select sleep(7) from dual', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
|
|
# Test the case that no query sent during tablet shuts down (single tablet)
|
|
def test_restart_mysql_tablet_idle(self):
|
|
self.replica_tablet2.kill_vttablet()
|
|
vtgate_conn = get_connection()
|
|
utils.wait_procs([self.replica_tablet.shutdown_mysql(),])
|
|
try:
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail('DatabaseError should have been raised')
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
|
|
utils.wait_procs([self.replica_tablet.start_mysql(),])
|
|
# force health check so tablet can become serving
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.replica_tablet.kill_vttablet()
|
|
self.tablet_start(self.replica_tablet, 'replica')
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
# TODO: expect to fail until we can detect vttablet shuts down gracefully
|
|
# while VTGate is idle.
|
|
# NOTE: with grpc, it will reconnect, and not trigger an error.
|
|
if protocols_flavor().tabletconn_protocol() == 'grpc':
|
|
return
|
|
try:
|
|
result = vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail(
|
|
'DatabaseError should have been raised, but got %s' % str(result))
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
|
|
# Test the case that there are queries sent during vttablet shuts down,
|
|
# and all querys fail because there is only one vttablet.
|
|
def test_restart_mysql_tablet_queries(self):
|
|
vtgate_conn = get_connection()
|
|
utils.wait_procs([self.replica_tablet.shutdown_mysql(),])
|
|
utils.wait_procs([self.replica_tablet2.shutdown_mysql(),])
|
|
try:
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail('DatabaseError should have been raised')
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
utils.wait_procs([self.replica_tablet.start_mysql(),])
|
|
utils.wait_procs([self.replica_tablet2.start_mysql(),])
|
|
# force health check so tablet can become serving
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet2.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
self.replica_tablet2.wait_for_vttablet_state('SERVING')
|
|
self.replica_tablet2.kill_vttablet()
|
|
self.replica_tablet.kill_vttablet(wait=False)
|
|
time.sleep(0.1)
|
|
# send query while vttablet is in lameduck, should fail as no vttablet
|
|
try:
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail('DatabaseError should have been raised')
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
# send another query, should also fail
|
|
try:
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail('DatabaseError should have been raised')
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
# sleep over the lameduck period
|
|
time.sleep(0.5)
|
|
self.tablet_start(self.replica_tablet, 'replica')
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
self.replica_tablet2.wait_for_vttablet_state('SERVING')
|
|
# as the cached vtgate-tablet conn was marked down, it should succeed
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
|
|
# Test the case that there are queries sent during one vttablet shuts down,
|
|
# and all querys succeed because there is another vttablet.
|
|
def test_restart_mysql_tablet_queries_multi_tablets(self):
|
|
vtgate_conn = get_connection()
|
|
utils.wait_procs([self.replica_tablet.shutdown_mysql(),])
|
|
# should retry on tablet2 and succeed
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
utils.wait_procs([self.replica_tablet.start_mysql(),])
|
|
# force health check so tablet can become serving
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
tablet2_vars = utils.get_vars(self.replica_tablet2.port)
|
|
t2_query_count_before = int(tablet2_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet2_vars = utils.get_vars(self.replica_tablet2.port)
|
|
t2_query_count_after = int(tablet2_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t2_query_count_after-t2_query_count_before) == 1)
|
|
# kill tablet2 and leave it in lameduck mode
|
|
self.replica_tablet2.kill_vttablet(wait=False)
|
|
time.sleep(0.1)
|
|
# send query while tablet2 is in lameduck, should retry on tablet1
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_before = int(tablet1_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_after = int(tablet1_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t1_query_count_after-t1_query_count_before) == 1)
|
|
# sleep over the lameduck period
|
|
time.sleep(0.5)
|
|
# send another query, should also succeed on tablet1
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_before = int(tablet1_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_after = int(tablet1_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t1_query_count_after-t1_query_count_before) == 1)
|
|
# start tablet2
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
self.replica_tablet2.wait_for_vttablet_state('SERVING')
|
|
# it should succeed on tablet1
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_before = int(tablet1_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_after = int(tablet1_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t1_query_count_after-t1_query_count_before) == 1)
|
|
|
|
# Test the case that there are queries sent during one vttablet is killed,
|
|
# and all queries succeed because there is another vttablet.
|
|
def test_kill_mysql_tablet_queries_multi_tablets(self):
|
|
vtgate_conn = get_connection()
|
|
utils.wait_procs([self.replica_tablet.shutdown_mysql(),])
|
|
# should retry on tablet2 and succeed
|
|
tablet2_vars = utils.get_vars(self.replica_tablet2.port)
|
|
t2_query_count_before = int(tablet2_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet2_vars = utils.get_vars(self.replica_tablet2.port)
|
|
t2_query_count_after = int(tablet2_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t2_query_count_after-t2_query_count_before) == 1)
|
|
# start tablet1 mysql
|
|
utils.wait_procs([self.replica_tablet.start_mysql(),])
|
|
# force health check so tablet can become serving
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
# should succeed on tablet2
|
|
tablet2_vars = utils.get_vars(self.replica_tablet2.port)
|
|
t2_query_count_before = int(tablet2_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet2_vars = utils.get_vars(self.replica_tablet2.port)
|
|
t2_query_count_after = int(tablet2_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t2_query_count_after-t2_query_count_before) == 1)
|
|
# hard kill tablet2
|
|
self.replica_tablet2.hard_kill_vttablet()
|
|
# send query after tablet2 is killed, should not retry on the cached conn
|
|
try:
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail('DatabaseError should have been raised')
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
# send another query, should succeed on tablet1
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_before = int(tablet1_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_after = int(tablet1_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t1_query_count_after-t1_query_count_before) == 1)
|
|
# start tablet2
|
|
self.tablet_start(self.replica_tablet2, 'replica')
|
|
self.replica_tablet2.wait_for_vttablet_state('SERVING')
|
|
# it should succeed on tablet1
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_before = int(tablet1_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_after = int(tablet1_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t1_query_count_after-t1_query_count_before) == 1)
|
|
|
|
def test_bind_vars_in_exception_message(self):
|
|
vtgate_conn = get_connection()
|
|
keyspace_id = None
|
|
|
|
count = 1
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'delete from vt_a', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[get_keyrange(SHARD_NAMES[self.shard_index])])
|
|
vtgate_conn.commit()
|
|
eid_map = {}
|
|
# start transaction
|
|
vtgate_conn.begin()
|
|
kid_list = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]]
|
|
|
|
# kill vttablet
|
|
self.master_tablet.kill_vttablet()
|
|
|
|
try:
|
|
# perform write, this should fail
|
|
for x in xrange(count):
|
|
keyspace_id = kid_list[x%len(kid_list)]
|
|
eid_map[x] = str(keyspace_id)
|
|
vtgate_conn._execute(
|
|
'insert into vt_a (eid, id, keyspace_id) '
|
|
'values (%(eid)s, %(id)s, %(keyspace_id)s)',
|
|
{'eid': x, 'id': x, 'keyspace_id': keyspace_id},
|
|
KEYSPACE_NAME, 'master', keyspace_ids=[pack_kid(keyspace_id)])
|
|
vtgate_conn.commit()
|
|
except Exception, e:
|
|
# check that bind var value is not present in exception message.
|
|
if str(keyspace_id) in str(e):
|
|
self.fail('bind_vars present in the exception message')
|
|
finally:
|
|
vtgate_conn.rollback()
|
|
# Start master tablet again
|
|
self.tablet_start(self.master_tablet, 'master')
|
|
|
|
def test_fail_fast_when_no_serving_tablets(self):
|
|
"""Verify VtGate requests fail-fast when tablets are unavailable.
|
|
|
|
When there are no SERVING tablets available to serve a request,
|
|
VtGate should fail-fast (returning an appropriate error) without
|
|
waiting around till the request deadline expires.
|
|
"""
|
|
tablet_type = 'replica'
|
|
keyranges = [get_keyrange(SHARD_NAMES[self.shard_index])]
|
|
query = 'select * from vt_insert_test'
|
|
|
|
# Execute a query to warm VtGate's caches for connections and endpoints
|
|
get_rtt(KEYSPACE_NAME, query, tablet_type, keyranges)
|
|
|
|
# Shutdown mysql and ensure tablet is in NOT_SERVING state
|
|
utils.wait_procs([self.replica_tablet.shutdown_mysql(),])
|
|
utils.wait_procs([self.replica_tablet2.shutdown_mysql(),])
|
|
|
|
try:
|
|
get_rtt(KEYSPACE_NAME, query, tablet_type, keyranges)
|
|
self.replica_tablet.wait_for_vttablet_state('NOT_SERVING')
|
|
self.replica_tablet2.wait_for_vttablet_state('NOT_SERVING')
|
|
except Exception:
|
|
self.fail('unable to set tablet to NOT_SERVING state')
|
|
|
|
# Fire off a few requests in parallel
|
|
num_requests = 10
|
|
pool = ThreadPool(processes=num_requests)
|
|
async_results = []
|
|
for _ in range(num_requests):
|
|
async_result = pool.apply_async(
|
|
get_rtt, (KEYSPACE_NAME, query, tablet_type, keyranges))
|
|
async_results.append(async_result)
|
|
|
|
# Fetch all round trip times and verify max
|
|
rt_times = []
|
|
for async_result in async_results:
|
|
rt_times.append(async_result.get())
|
|
# The true upper limit is 2 seconds (1s * 2 retries as in
|
|
# utils.py). To account for network latencies and other variances,
|
|
# we keep an upper bound of 3 here.
|
|
self.assertTrue(
|
|
max(rt_times) < 3,
|
|
'at least one request did not fail-fast; round trip times: %s' %
|
|
rt_times)
|
|
|
|
# Restart tablet and put it back to SERVING state
|
|
utils.wait_procs([self.replica_tablet.start_mysql(),])
|
|
utils.wait_procs([self.replica_tablet2.start_mysql(),])
|
|
# force health check so tablet can become serving
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet2.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
self.replica_tablet2.wait_for_vttablet_state('SERVING')
|
|
|
|
def test_lameduck_ongoing_query_single(self):
|
|
vtgate_conn = get_connection()
|
|
utils.wait_procs([self.replica_tablet2.shutdown_mysql(),])
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet2.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet.kill_vttablet()
|
|
self.tablet_start(self.replica_tablet, 'replica', '5s')
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
# make sure query can go through tablet1
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_before = int(tablet1_vars['Queries']['TotalCount'])
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
tablet1_vars = utils.get_vars(self.replica_tablet.port)
|
|
t1_query_count_after = int(tablet1_vars['Queries']['TotalCount'])
|
|
self.assertTrue((t1_query_count_after-t1_query_count_before) == 1)
|
|
# start a long running query
|
|
num_requests = 10
|
|
pool = ThreadPool(processes=num_requests)
|
|
async_results = []
|
|
for _ in range(5):
|
|
async_result = pool.apply_async(
|
|
send_long_query, (KEYSPACE_NAME, 'replica', [self.keyrange], 2))
|
|
async_results.append(async_result)
|
|
# soft kill vttablet
|
|
# **should wait till previous queries are sent out**
|
|
time.sleep(1)
|
|
self.replica_tablet.kill_vttablet(wait=False)
|
|
# send query while vttablet is in lameduck, should fail as no vttablet
|
|
time.sleep(0.1)
|
|
try:
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
self.fail('DatabaseError should have been raised')
|
|
except Exception, e:
|
|
self.assertIsInstance(e, dbexceptions.DatabaseError)
|
|
self.assertNotIsInstance(e, dbexceptions.IntegrityError)
|
|
self.assertNotIsInstance(e, dbexceptions.OperationalError)
|
|
self.assertNotIsInstance(e, dbexceptions.TimeoutError)
|
|
# Fetch all ongoing query results
|
|
query_results = []
|
|
for async_result in async_results:
|
|
query_results.append(async_result.get())
|
|
# all should succeed
|
|
for query_result in query_results:
|
|
self.assertTrue(query_result)
|
|
# sleep over lameduck period
|
|
time.sleep(5)
|
|
# start tablet1
|
|
self.tablet_start(self.replica_tablet, 'replica')
|
|
self.replica_tablet.wait_for_vttablet_state('SERVING')
|
|
# send another query, should succeed on tablet1
|
|
vtgate_conn._execute(
|
|
'select 1 from vt_insert_test', {},
|
|
KEYSPACE_NAME, 'replica',
|
|
keyranges=[self.keyrange])
|
|
# start tablet2
|
|
utils.wait_procs([self.replica_tablet2.start_mysql(),])
|
|
utils.run_vtctl(
|
|
['RunHealthCheck', self.replica_tablet2.tablet_alias, 'replica'],
|
|
auto_log=True)
|
|
self.replica_tablet2.wait_for_vttablet_state('SERVING')
|
|
|
|
|
|
# Return round trip time for a VtGate query, ignore any errors
|
|
def get_rtt(keyspace, query, tablet_type, keyranges):
|
|
vtgate_conn = get_connection()
|
|
cursor = vtgate_conn.cursor(keyspace, tablet_type, keyranges=keyranges)
|
|
start = time.time()
|
|
try:
|
|
cursor.execute(query, {})
|
|
except Exception:
|
|
pass
|
|
duration = time.time() - start
|
|
return duration
|
|
|
|
|
|
# Send out a long query, return if it succeeds.
|
|
def send_long_query(keyspace, tablet_type, keyranges, delay):
|
|
try:
|
|
vtgate_conn = get_connection()
|
|
cursor = vtgate_conn.cursor(keyspace, tablet_type, keyranges=keyranges)
|
|
query = 'select sleep(%s) from dual' % str(delay)
|
|
try:
|
|
cursor.execute(query, {})
|
|
except Exception:
|
|
return False
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
class VTGateTestLogger(vtdb_logger.VtdbLogger):
|
|
|
|
def __init__(self):
|
|
self._integrity_error_count = 0
|
|
|
|
def integrity_error(self, e):
|
|
self._integrity_error_count += 1
|
|
|
|
def get_integrity_error_count(self):
|
|
return self._integrity_error_count
|
|
|
|
|
|
DML_KEYWORDS = ['insert', 'update', 'delete']
|
|
|
|
|
|
class TestExceptionLogging(BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestExceptionLogging, self).setUp()
|
|
self.shard_index = 1
|
|
self.keyrange = get_keyrange(SHARD_NAMES[self.shard_index])
|
|
self.master_tablet = shard_1_master
|
|
self.replica_tablet = shard_1_replica1
|
|
vtdb_logger.register_vtdb_logger(VTGateTestLogger())
|
|
self.logger = vtdb_logger.get_logger()
|
|
|
|
def test_integrity_error_logging(self):
|
|
vtgate_conn = get_connection()
|
|
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'delete from vt_a', {},
|
|
KEYSPACE_NAME, 'master',
|
|
keyranges=[self.keyrange])
|
|
vtgate_conn.commit()
|
|
|
|
keyspace_id = SHARD_KID_MAP[SHARD_NAMES[self.shard_index]][0]
|
|
|
|
old_error_count = self.logger.get_integrity_error_count()
|
|
try:
|
|
vtgate_conn.begin()
|
|
vtgate_conn._execute(
|
|
'insert into vt_a (eid, id, keyspace_id) '
|
|
'values (%(eid)s, %(id)s, %(keyspace_id)s)',
|
|
{'eid': 1, 'id': 1, 'keyspace_id': keyspace_id}, KEYSPACE_NAME,
|
|
'master', keyspace_ids=[pack_kid(keyspace_id)])
|
|
vtgate_conn._execute(
|
|
'insert into vt_a (eid, id, keyspace_id) '
|
|
'values (%(eid)s, %(id)s, %(keyspace_id)s)',
|
|
{'eid': 1, 'id': 1, 'keyspace_id': keyspace_id}, KEYSPACE_NAME,
|
|
'master', keyspace_ids=[pack_kid(keyspace_id)])
|
|
vtgate_conn.commit()
|
|
except dbexceptions.IntegrityError as e:
|
|
parts = str(e).split(',')
|
|
exc_msg = parts[0]
|
|
for kw in DML_KEYWORDS:
|
|
if kw in exc_msg:
|
|
self.fail("IntegrityError shouldn't contain the query %s" % exc_msg)
|
|
except Exception as e:
|
|
self.fail('Expected IntegrityError to be raised, raised %s' % str(e))
|
|
finally:
|
|
vtgate_conn.rollback()
|
|
# The underlying execute is expected to catch and log the integrity error.
|
|
self.assertEqual(self.logger.get_integrity_error_count(), old_error_count+1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
utils.main()
|