cetus/scripts/proxy-client

950 lines
38 KiB
Plaintext
Raw Permalink Normal View History

2018-03-06 14:00:39 +08:00
#!/usr/bin/env python
# coding: utf-8
###
# A script console for cetus.
# For changing proxy config on remote server.
###
import os
import sys
import re
import traceback
import MySQLdb
import json
###
# DB settings
#
# Please EDIT this before running
###
HOST = '10.238.7.6'
PORT = 3306
USER = 'root'
PASSWD = '123456'
DB = 'proxy_management'
conn = None
PROXY_ID = None
BACKENDS_NDX_MAP = {}
# a pattern for check IP format
IP_RE_PATTERN = ('^(?:[1-9]?\d|1\d{2}|2(?:[0-4]\d|5[0-5]))'
'(?:\.(?:[1-9]?\d|1\d{2}|2(?:[0-4]\d|5[0-5]))){3}$')
# Print msg to console with color
def print_console(msg):
print('{0}{1}{2}'.format('\033[1;32m', msg, '\033[0m'))
def strwide(msg):
return '\n{0}\n'.format(msg)
def print_console_wide(msg):
print_console(strwide(msg))
# The decoration for reconnecting db
def reconnect(f):
def deco(*args, **kwargs):
global conn
if not conn:
conn = connect_db(HOST, PORT, USER, PASSWD, DB)
return f(*args, **kwargs)
return deco
# Connect to database
def connect_db(host, port, user, passwd, db):
return MySQLdb.Connection(
host=host,
port=port,
user=user,
passwd=passwd,
db=db,
charset='utf8')
# Select data
@reconnect
def query_data(sql):
global conn
cur = conn.cursor()
cur.execute(sql)
data = cur.fetchall()
cur.close()
return data
@reconnect
def query(sql):
global conn
cur = conn.cursor()
ret = cur.execute(sql)
conn.commit()
cur.close()
return ret
@reconnect
def querymany(sql, data):
global conn
cur = conn.cursor()
ret = cur.executemany(sql, data)
conn.commit()
cur.close()
return ret
@reconnect
def insertone(sql):
global conn
cur = conn.cursor()
ret = 0
try:
cur.execute(sql)
ret = cur.lastrowid
except MySQLdb.Error:
pass
finally:
conn.commit()
cur.close()
return ret
def gen_backend_ndx():
global BACKENDS_NDX_MAP
BACKENDS_NDX_MAP = {}
# Process user's command
def run_cmd(cmd):
global PROXY_ID
global BACKENDS_NDX_MAP
###
# Show simple settings for proxy to start
###
if cmd == 'show':
cmap = {}
data = query_data('select proxy_id,s_key,s_value from settings')
max_key = 0
for line in data:
id, k, v = line
if not id in cmap:
cmap[id] = {}
if not k in cmap[id]:
cmap[id][k] = []
cmap[id][k].append(v)
if len(k) > max_key:
max_key = len(k)
msg_list = ['']
for ndx in cmap:
msg_list.append('-'*10)
msg_list.append('PROXY_ID: {0}'.format(ndx))
msg_list.append('')
for k in sorted(cmap[ndx].keys()):
i = 0
for v in cmap[ndx][k]:
if i is 0:
msg_list.append('{0}{1}'.format(k.ljust(max_key+3), v))
else:
msg_list.append('{0}{1}'.format(''.ljust(max_key+3), v))
i += 1
msg_list.append('-'*10)
msg_list.append('')
msg = '\n'.join(msg_list)
###
# Show backends info details
###
elif cmd == 'show backends':
data = query_data(
'select a.id, a.proxy_id, a.s_key, a.s_value, b.stat from '
'settings as a, backends as b where a.s_key in '
'("proxy-backend-addresses", "proxy-read-only-backend-addresses")'
' and a.id=b.settings_id')
cmap = {}
BACKENDS_NDX_MAP = {}
ndx_map = {}
for line in data:
id, pxy_id, k, v, st = line
if not pxy_id in BACKENDS_NDX_MAP:
BACKENDS_NDX_MAP[pxy_id] = {}
ndx_map[pxy_id] = 1
BACKENDS_NDX_MAP[pxy_id][str(ndx_map[pxy_id])] = id
if not pxy_id in cmap:
cmap[pxy_id] = []
bk_addr_list = re.split('[@:]', v)
addr = bk_addr_list[0]
port = bk_addr_list[1]
if k == 'proxy-backend-addresses':
tp = 'rw'
else:
tp = 'ro'
cmap[pxy_id].append({'ndx': ndx_map[pxy_id], 'addr':addr, 'port':port, 'type':tp, 'stat':st})
ndx_map[pxy_id] += 1
msg_list = ['']
for pxy_id in cmap:
msg_list.append('-'*10)
msg_list.append('PROXY_ID: {0}'.format(pxy_id))
msg_list.append('')
for backend in cmap[pxy_id]:
msg_list.append('{0}{1}'.format('NDX'.ljust(8), backend['ndx']))
msg_list.append('{0}{1}'.format('ADDR'.ljust(8), backend['addr']))
msg_list.append('{0}{1}'.format('PORT'.ljust(8), backend['port']))
msg_list.append('{0}{1}'.format('TYPE'.ljust(8), backend['type']))
msg_list.append('{0}{1}'.format('STAT'.ljust(8), backend['stat']))
msg_list.append('')
msg_list[-1] = '-'*10
msg_list.append('')
msg = '\n'.join(msg_list)
###
# Show sharding info details
###
elif cmd == 'show sharding':
data = query_data('select proxy_id, shard_db, shard_table, pkey, shard_key_type, '
'method, partitions, logic_shard_num from sharding ')
shard_map = {}
for line in data:
proxy_id = line[0]
if not proxy_id in shard_map:
shard_map[proxy_id] = []
shard_map[proxy_id].append({
'shard_db': line[1],
'shard_table': line[2],
'pkey': line[3],
'shard_key_type': line[4],
'method': line[5],
'partitions': line[6],
'logic_shard_num': line[7]
})
msg_list = ['']
for pid in shard_map:
msg_list.append('PROXY_ID: {0}'.format(pid))
msg_list.append('='*20)
for shard in shard_map[pid]:
if shard['shard_db'] == '_default_group':
msg_list.append('{0}{1}'.format('DEFAULT_GROUP'.ljust(18), shard['partitions']))
elif shard['shard_db'] == '_all_groups':
msg_list.append('{0}{1}'.format('ALL_GROUPS'.ljust(18), shard['partitions']))
else:
msg_list.append('{0}{1}'.format('DB'.ljust(18), shard['shard_db']))
msg_list.append('{0}{1}'.format('TABLE'.ljust(18), shard['shard_table']))
msg_list.append('{0}{1}'.format('KEY'.ljust(18), shard['pkey']))
msg_list.append('{0}{1}'.format('KEYTYPE'.ljust(18), shard['shard_key_type']))
msg_list.append('{0}{1}'.format('METHOD'.ljust(18), shard['method']))
msg_list.append('{0}{1}'.format('PARTITIONS'.ljust(18), shard['partitions']))
msg_list.append('{0}{1}'.format('SHARDNUM'.ljust(18), shard['logic_shard_num']))
msg_list.append('-'*10)
msg_list.append('')
msg = '\n'.join(msg_list)
###
# set global proxy_id, can ignore proxy_id in where clause below.
###
elif cmd.startswith('use '):
try:
PROXY_ID = cmd.split(' ', 1)[1]
msg = strwide('Proxy changed.')
except IndexError:
msg = strwide('Unknown Proxy.')
###
# Add or update settings
###
elif cmd.startswith('add ') or cmd.startswith('update '):
syntax_error = 0
msg = ''
###
# RE Pattern for add/update
# match `update (cap1) set (cap2) where (cap3)`
# Consider a JSON string may contains any character
###
up_pat = ('^\s*(add|update)\s+([a-zA-Z_]+)\s+'
'set\s+(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\4|\d+)\s*'
'(?:,\s*\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\5|\d+)\s*)*)'
'(?:\s+where\s+(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\7|\d+)'
'(?:\s+and\s+\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\8|\d+))*))?\s*$')
res = re.match(up_pat, cmd, re.IGNORECASE)
# Parse update command
if res is None:
syntax_error = 1
else:
c0 = res.group(1)
# settings/backend/sharding
c1 = res.group(2)
# set key/value
c2 = res.group(3)
# where clause, can be None if `use proxy_id` is set
c3 = res.group(6)
pxy = PROXY_ID
set_map = {}
cond_map = {}
# Parse set clause
set_pat = '(?:^|\s+)(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\2|\d+))(?:\s+|,|$)'
for set_group in re.findall(set_pat, c2):
set_item = set_group[0]
k, v = set_item.split('=', 1)
k = k.strip().lower()
v = v.strip()
# If string is in single/double quotation, remove them
if re.match(r'^(\'|").*?\1$', v):
v = v[1:-1]
set_map[k] = v
# Parse where clause if exists
if c3 is not None:
where_pat = '(?:^|\s+)(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\2|\d+))(?:\s+|$)'
for where_group in re.findall(where_pat, c3):
where_item = where_group[0]
k, v = where_item.split('=')
k = k.strip().lower()
v = v.strip()
if re.match(r'^(\'|").*?\1$', v):
v = v[1:-1]
cond_map[k] = v
if 'proxy_id' in cond_map:
pxy = cond_map['proxy_id']
# proxy_id should be set in where clause or use
if not pxy:
msg = strwide('`PROXY_ID` need to be specified')
elif c0 == 'add':
r = 0
if c1 == 'settings':
for k, v in set_map.items():
if not k in ['plugins', 'proxy-backend-addresses',
'proxy-read-only-backend-addresses']:
exist = query_data(
('select id from settings where proxy_id="{0}"'
' and s_key="{1}"').format(pxy, k))
if exist:
print_console('"{0}" currently exists, use `update` instead.')
continue
r += query(
('insert into settings set proxy_id="{0}", s_key="{1}",'
's_value="{2}"').format(pxy, k, v))
elif c1 == 'backend':
if not 'addr' in set_map:
return strwide('"addr" is needed.')
bk_addr = set_map['addr']
if not re.match(IP_RE_PATTERN, bk_addr):
return strwide('IP format error.')
bk_port = set_map.get('port', '3306')
if int(bk_port) > 65535 or int(bk_port) < 0:
return strwide('PORT format error.\nShould be integer in 0~65535')
bk_type = set_map.get('type', 'ro')
if not bk_type in ['ro', 'rw']:
return strwide("TYPE format error.\nShould be 'rw' or 'ro'.")
bk_stat = set_map.get('stat', 'unknown')
if not bk_stat in ['unknown', 'up', 'down', 'delete', 'maintaining']:
return strwide("STAT format error.\n"
"Should be 'unknown', 'up', 'down', 'delete' or 'maintaining'")
bk_shard = set_map.get('shard_group', None)
exist = query_data(
('select a.s_key, a.s_value, b.stat from settings as a, backends as b'
' where s_key in ("proxy-backend-addresses", "proxy-read-only-backend-addresses")'
' and a.id=b.settings_id and proxy_id="{0}"').format(pxy))
bk_full_addr = '{0}:{1}'.format(bk_addr, bk_port)
if bk_shard:
bk_full = '{0}@{1}'.format(bk_full_addr, bk_shard)
else:
bk_full = bk_addr_list
for bk in exist:
if bk[1] == bk_full_addr or bk[1].startswith('{0}@'.format(bk_full_addr)):
return strwide('backend "{0}:{1}" exists'.format(bk_addr, bk_port))
if bk_type == 'rw':
bk_key = 'proxy-backend-addresses'
else:
bk_key = 'proxy-read-only-backend-addresses'
last_id = insertone(
('insert into settings set proxy_id="{0}", '
's_key="{1}", s_value="{2}"').format(pxy, bk_key, bk_full))
if last_id:
r += 1
if insertone('insert into backends set settings_id={0}, stat="{1}"'.format(
last_id, bk_stat)):
r += 1
else:
query('delete from settings where id={0}'.format(last_id))
print_console('Insert failed. Dirty data exists in table backends.')
elif c1 == 'sharding':
shard_keys = set_map.keys()
for k in ['default_group', 'all_groups']:
if k in shard_keys:
shard_keys.remove(k)
real_k = '_' + k
v = set_map[k]
exist = query_data(
('select id from sharding where proxy_id="{0}" and '
'shard_db="{1}"').format(pxy, real_k))
try:
json.loads(v)
except ValueError:
print_console('"{0}" value should be in JSON format.'.format(real_k))
continue
if exist:
print_console('"{0}" exists, skip.'.format(real_k))
continue
in_sql = ('insert into sharding set proxy_id="{0}",'
'shard_db="{1}", shard_table="{1}", partitions="{2}"'
).format(pxy, real_k, v.replace('"', '\\"'))
r += query(in_sql)
# Check if all params is set.
if shard_keys:
for kk in ['db', 'table', 'key', 'keytype', 'method',
'partitions', 'shardnum']:
if not kk in set_map:
return strwide('Lack parameter "{0}".'.format(kk))
if not set_map['keytype'] in ['int', 'str', 'datetime', 'date']:
return strwide('"keytype" should be [int|str|date|datetime].')
if not set_map['method'] in ['range', 'hash']:
return strwide('"method" should be hash or range.')
try:
json.loads(set_map['partitions'])
except ValueError:
return strwide('"partitions" should be in JSON format.')
set_map['partitions'] = set_map['partitions'].replace('"', '\\"')
r += query(
('insert into sharding set proxy_id="{0}", shard_db="{1[db]}",'
'shard_table="{1[table]}", pkey="{1[key]}", shard_key_type='
'"{1[keytype]}", method="{1[method]}", partitions="{1[partitions]}",'
'logic_shard_num="{1[shardnum]}"').format(pxy, set_map))
else:
syntax_error = 1
print_console_wide('{0} rows added.'.format(r))
elif c0 == 'update':
r = 0
# Set normal start config
if c1 == 'settings':
for k, v in set_map.items():
exist = query_data(('select id from settings where proxy_id="{0}"'
' and s_key="{1}"').format(pxy, k))
if exist and exist[0]:
r += query('update settings set s_value="{0}" where id={1}'\
.format(v, exist[0][0]))
else:
print_console('"{0}" does not exist, use `add settings` instead.')
print_console_wide('{0} rows changed.'.format(r))
# Set backends config
elif c1 == 'backend':
# `show backends` should be run before update
# so that backend ndx will be create
if not BACKENDS_NDX_MAP:
msg = strwide('Please type `show backends` to confirm backend index.')
elif not 'ndx' in cond_map:
msg = strwide("'ndx' should be specified.")
else:
try:
backend = query_data((
'select a.s_key, a.s_value, b.stat from settings as a,'
'backends as b where a.id={0} and a.id=b.settings_id'
).format(BACKENDS_NDX_MAP[pxy][cond_map['ndx']]))
except IndexError:
return strwide('PROXY_ID or NDX error.')
if backend and backend[0]:
bk_spl = re.split('[@:]', backend[0][1])
bk_addr = bk_spl[0]
bk_port = bk_spl[1]
if len(bk_spl) is 3:
bk_shard = bk_spl[2]
else:
bk_shard = None
if backend[0][0] == 'proxy-backend-addresses':
bk_type = 'rw'
else:
bk_type = 'ro'
bk_stat = backend[0][2]
else:
return strwide('Backend info error.\n'
'Retry `show backends` and update again.')
for k, v in set_map.items():
if k == 'addr':
if not re.match(IP_RE_PATTERN, v):
return strwide('IP format error.')
bk_addr = v
elif k == 'port':
try:
new_port = int(v)
if new_port > 65535:
raise ValueError
except ValueError:
return strwide('PORT format error.\nShould be integer in 0~65535')
bk_port = v
elif k == 'type':
if v != 'rw' and v != 'ro':
print v
return strwide("TYPE format error.\nShould be 'rw' or 'ro'.")
bk_type = v
elif k == 'stat':
if not v in ['unknown', 'up', 'down', 'delete', 'maintaining']:
return strwide("STAT format error.\n"
"Should be 'unknown', 'up', 'down', 'delete' or 'maintaining'")
bk_stat = v
elif k == 'shard_group':
bk_shard = v
else:
return strwide("Backends setting key should be either 'addr', 'port', 'type' or 'stat'.")
if bk_shard:
new_bk = '{0}:{1}@{2}'.format(bk_addr, bk_port, bk_shard)
else:
new_bk = ':'.join([bk_addr, bk_port])
if bk_type == 'rw':
bk_key = 'proxy-backend-addresses'
else:
bk_key = 'proxy-read-only-backend-addresses'
r += query('update settings set s_key="{0}", s_value="{1}" where id={2}'.format(
bk_key, new_bk, BACKENDS_NDX_MAP[pxy][cond_map['ndx']]))
r += query('update backends set stat="{0}" where settings_id={1}'.format(
bk_stat, BACKENDS_NDX_MAP[pxy][cond_map['ndx']]))
print_console_wide('{0} rows changed.'.format(r))
msg_list = ['']
msg_list.append('PROXY_ID: {0}'.format(pxy))
msg_list.append('')
msg_list.append('{0}{1}'.format('NDX'.ljust(8), cond_map['ndx']))
msg_list.append('{0}{1}'.format('ADDR'.ljust(8), bk_addr))
msg_list.append('{0}{1}'.format('PORT'.ljust(8), bk_port))
msg_list.append('{0}{1}'.format('TYPE'.ljust(8), bk_type))
msg_list.append('{0}{1}'.format('STAT'.ljust(8), bk_stat))
msg_list.append('')
msg = '\n'.join(msg_list)
elif c1 == 'sharding':
# shard settings for table need 'db' and 'table'
shard_keys = set_map.keys()
try:
shard_keys.remove('default_group')
shard_keys.remove('all_groups')
except ValueError:
pass
if shard_keys and (not 'db' in cond_map or not 'table' in cond_map):
return strwide('"db" and "table" are needed in where clause')
# set 'default_group' and 'all_groups'
if 'default_group' in set_map:
r += query(('update sharding set partitions="{0}" where proxy_id="{1}"'
' and shard_db="_default_group"').format(
set_map['default_group'].replace('"', '\\"'), pxy))
if 'all_groups' in set_map:
r += query(('update sharding set partitions="{0}" where proxy_id="{1}"'
' and shard_db="_all_groups"').format(
set_map['all_groups'].replace('"', '\\"'), pxy))
# Normal shard settings
for k in shard_keys:
exist = query_data(('select id from sharding where proxy_id={0}'
' and shard_db="{1}" and shard_table="{2}"').format(
pxy, cond_map['db'], cond_map['table']))
if not exist or not exist[0]:
return strwide('shard rule does not exists, use "add sharding" instead')
rule_id = exist[0][0]
shard_set_list = []
for k, v in set_map.items():
if k == 'db':
shard_set_list.append('shard_db="{0}"'.format(v))
elif k == 'table':
shard_set_list.append('shard_table="{0}"'.format(v))
elif k == 'key':
shard_set_list.append('pkey="{0}"'.format(v))
elif k == 'keytype':
if not v in ['int', 'str', 'datetime', 'date']:
return strwide('"keytype" should be [int|str|date|datetime]')
shard_set_list.append('shard_key_type="{0}"'.format(v))
elif k == 'method':
if not v in ['range', 'hash']:
return strwide('"method" should be hash or range')
shard_set_list.append('method="{0}"'.format(v))
elif k == 'partitions':
try:
json.loads(v)
except ValueError:
return strwide('"partitions" should be a json string')
shard_set_list.append('partitions="{0}"'.format(v.replace('"', '\\"')))
r += query('update sharding set {0} where id={id}'.format(
','.join(shard_set_list), id=rule_id))
print_console_wide('{0} rows changed.'.format(r))
else:
syntax_error = 1
if syntax_error is 1:
msg = strwide('Syntax error with command `{0}`').format(cmd)
###
# delete settings, include sharding and backend
###
elif cmd.startswith('remove '):
syntax_error = 0
msg = ''
r = 0
rm_pat = ('^\s*remove\s+([a-zA-Z_]+)\s+'
'where\s+(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\3|\d+)'
'(?:\s+and\s+\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\4|\d+))*)\s*$')
res = re.match(rm_pat, cmd, re.IGNORECASE)
if res is None:
syntax_error = 1
else:
c1 = res.group(1)
c2 = res.group(2)
pxy = PROXY_ID
cond_map = {}
# Parse where clause
where_pat = '(?:^|\s+)(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\2|\d+))(?:\s+|$)'
for where_group in re.findall(where_pat, c2):
where_item = where_group[0]
k, v = where_item.split('=')
k = k.strip().lower()
v = v.strip()
if re.match(r'^(\'|").*?\1$', v):
v = v[1:-1]
cond_map[k] = v
if 'proxy_id' in cond_map:
pxy = cond_map['proxy_id']
if not pxy:
msg = strwide('`PROXY_ID` need to be specified')
else:
if c1 == 'settings':
for k, v in cond_map.items():
del_sql = 'delete from settings where proxy_id="{0}"'.format(pxy)
if 'key' in cond_map:
del_sql += ' and s_key="{0}"'.format(cond_map['key'])
if 'value' in cond_map:
del_sql += ' and s_value="{0}"'.format(cond_map['value'])
r += query(del_sql)
elif c1 == 'backend':
if not BACKENDS_NDX_MAP:
msg = strwide('Please type `show backends` to confirm backend index.')
elif not 'ndx' in cond_map:
msg = strwide("'ndx' should be specified.")
else:
try:
bk_id = BACKENDS_NDX_MAP[pxy][cond_map['ndx']]
r += query('delete from settings where id={0}'.format(bk_id))
r += query('delete from backends where settings_id={0}'
.format(bk_id))
except IndexError:
return strwide('PROXY_ID or NDX error.')
elif c1 == 'sharding':
if not 'db' in cond_map or not 'table' in cond_map:
return strwide('"db" and "table" must be specified.')
del_sql = ('delete from sharding where proxy_id="{0}"'
' and shard_db="{1}" and shard_table="{2}"'.format(
pxy, cond_map['db'], cond_map['table']))
r += query(del_sql)
else:
syntax_error = 1
if syntax_error is 1:
msg = strwide('Syntax error with command `{0}`').format(cmd)
else:
print_console_wide('{0} rows deleted.'.format(r))
###
# Set a flag in mysql so that proxy will reload when detect
###
elif cmd.startswith('apply ') or cmd == 'apply':
pxy = PROXY_ID
try:
proxy_id = cmd.split()[1].strip('\'" ')
except IndexError:
proxy_id = None
if proxy_id:
pxy = proxy_id
if not pxy:
return strwide('proxy_id should be specified.')
query('update switch set apply_switch=1 where proxy_id="{0}"'.format(pxy))
msg = strwide('Apply config success.\nProxy will reload in a short while.')
###
# Initialize new proxy settings
###
elif cmd.startswith('init '):
proxy_id = cmd.split()[1]
if not proxy_id:
return strwide('No `proxy_id` specified.')
try:
r = init_new(proxy_id)
except EOFError:
print_console('User quit.')
return ''
if r is 0:
print_console('User quit.')
return ''
msg = strwide('Init complete.')
###
# Delete proxy settings
###
elif cmd.startswith('delete '):
proxy_id = cmd.split()[1]
r = delete_one(proxy_id)
if r:
msg = strwide('Delete done.')
else:
msg = ''
###
# Create tables which store settings if not exist
###
elif cmd == 'create tables':
if create_tables() is 0:
msg = 'Create tables DONE!'
else:
msg = 'Error!'
###
# Show help message
###
elif cmd == 'h' or cmd == 'help':
msg = show_help()
###
# Quit script
###
elif cmd == 'q' or cmd == 'quit':
#sys.exit()
raise EOFError
###
# Unrecognized command
###
else:
msg = strwide('Unkonwn command `{0}`.\n'
'Type `help` to see command list.').format(cmd)
return msg
# Init settings for a proxy instance
def init_new(id):
init = init_fetchone('Init {0}? [y/N]: '.format(id), 'n')
if not init:
return 0
settings = {}
settings['proxy-address'] = init_fetchone(
'proxy-address=? [127.0.0.1:4440]: ', '127.0.0.1:4440', False)
settings['proxy-backend-addresses'] = init_fetchmulti(
'proxy-backend-addresses=? [127.0.0.1:3306]: ', '127.0.0.1:3306')
settings['log-file'] = init_fetchone('log-file=? []: ', '', False)
settings['plugin-dir'] = init_fetchone('plugin-dir=? []: ', '', False)
admin = init_fetchone('Set admin? [Y/n]: ', 'y', )
if admin:
plugins = ['admin', 'proxy']
settings['admin-address'] = init_fetchone(
'admin-address=? [127.0.0.1:4441]: ', '127.0.0.1:4441', False)
settings['admin-lua-script'] = init_fetchone('admin-lua-script=? []: ', '', False)
settings['admin-username'] = init_fetchone(
'admin-username=? [admin]: ', 'admin', False)
settings['admin-password'] = init_fetchone(
'admin-password=? [admin_pass]: ', 'admin_pass', False)
else:
plugins = ['proxy']
settings['plugins'] = plugins
settings['default-username'] = init_fetchone('default-username=? [root]: ', 'root', False)
settings['default-db'] = init_fetchone('default-db=? [test]: ', 'test', False)
settings['default-pool-size'] = init_fetchone(
'default-pool-size=? [50]: ', '50', False, check_func=check_int)
settings['user-pwd'] = init_fetchone('user-pwd=? [root@123456]: ', 'root@123456', False)
settings['auto-connect'] = init_fetchone('auto-connect=? [Y/n]: ', 'y')
settings['app-user-pwd'] = init_fetchone('app-user-pwd=? [root@123]: ', 'root@123', False)
settings['sharding-mode'] = init_fetchone('sharding-mode=? [y/N]: ', 'n')
settings['log-level'] = init_fetchone('log-level=? [debug]: ', 'debug', False)
print_console('\n'+'-'*20)
print_console('Init confirmation:')
query_data = []
for k in sorted(settings.keys()):
v = settings[k]
print('{0}={1}'.format(k, v))
if isinstance(v, list):
for v_obj in v:
query_data.append((k, v_obj))
else:
query_data.append((k, v))
print_console('-'*20+'\n')
if not init_fetchone('Continue? [Y/n]: ', 'y'):
return 0
init_sql = 'insert into settings set proxy_id="{0}", s_key=%s, s_value=%s'.format(id)
querymany(init_sql, query_data)
# create switch
query('insert into switch set proxy_id="{0}", apply_switch=0'.format(id))
return
def init_fetchone(intro, default, yn=True, check_func=None):
while 1:
r = raw_input(intro)
if r == '':
r = default
if yn:
r = r.lower()
if r == 'y':
return 1
elif r == 'n':
return 0
else:
print('Input error, type `y` or `n`')
continue
else:
if check_func is not None and not check_func(r):
print('Value error.')
continue
return r
def init_fetchmulti(intro, default):
ret = []
while 1:
r = raw_input(intro)
if r == '':
r = default
ret.append(r)
r_next = init_fetchone('Another one? [y/N]: ', 'n')
if not r_next:
break
return ret
def check_int(n):
try:
int(n)
except ValueError:
print('Integer is needed')
return 0
return 1
# Delete settings for ONE proxy
def delete_one(id):
r = init_fetchone('Delete {0}? (Cannot recover) [y/N]: ', 'n')
if r:
ret = query('delete from settings where proxy_id={0}'.format(id))
print_console_wide('{0} rows changed.'.format(ret))
return r
# Show prompt message
def show_prompt():
print_console_wide('### Welcome to Mysql-Proxy Console. ###')
# Create db tables
def create_tables():
csql = []
csql.append(
'CREATE TABLE IF NOT EXISTS `settings` ('
'`id` int(11) NOT NULL AUTO_INCREMENT,'
'`proxy_id` varchar(64) NOT NULL,'
'`s_key` varchar(64) NOT NULL,'
'`s_value` varchar(255) NOT NULL,'
'PRIMARY KEY (`id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
csql.append(
'CREATE TABLE IF NOT EXISTS `sharding` ('
'`id` int(11) NOT NULL AUTO_INCREMENT,'
'`proxy_id` varchar(64) NOT NULL,'
'`shard_db` varchar(32) NOT NULL,'
'`shard_table` varchar(64) NOT NULL,'
'`pkey` varchar(32) DEFAULT NULL,'
"`shard_key_type` enum('int','str','datetime') DEFAULT NULL,"
"`method` enum('hash','range') DEFAULT NULL,"
'`partitions` varchar(255) NOT NULL,'
'`logic_shard_num` int(11) DEFAULT NULL,'
'PRIMARY KEY (`id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
csql.append(
'CREATE TABLE IF NOT EXISTS `backends` ('
'`id` int(11) NOT NULL AUTO_INCREMENT,'
'`settings_id` int(11) DEFAULT NULL,'
'`stat` varchar(32) NOT NULL,'
'PRIMARY KEY (`id`),'
'UNIQUE KEY `settings_id` (`settings_id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
csql.append(
'CREATE TABLE IF NOT EXISTS `switch` ('
'`proxy_id` varchar(64) NOT NULL,'
"`apply_switch` tinyint(4) NOT NULL DEFAULT '0',"
'UNIQUE KEY `proxy_id` (`proxy_id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
# Ignore table exists warning
from warnings import filterwarnings
filterwarnings('ignore', category=MySQLdb.Warning)
for sql in csql:
query(sql)
return 0
# Show help message
def show_help():
return '''
List all commands:
show Show all config
show backends Show backends config
show sharding Show sharding config
add settings set [option]=[value], ... where proxy_id=[id]
update settings set [key]=[value] where proxy_id=[id]
remove settings where proxy_id=[id] and key=[value] and value=[value]
add backend set [option]=[value], ... where proxy_id=[id]
update backend set [option]=[value], ... where proxy_id=[id] and ndx=[index]
remove backend where proxy_id=[id] and ndx=[index]
add sharding set [option]=[value], ... where proxy_id=[id]
update sharding set [option]=[value], ... where proxy_id=[id] and db=[value] and table=[value]
remove sharding where proxy_id=[id] and db=[value] and table=[value]
init [proxy_id] Init config for `proxy_id`
use [proxy_id] Can ignore "where proxy_id=..." at follow when add/update/remove
delete [proxy_id] Delete config for `proxy_id`
apply [proxy_id] Apply config to a proxy
create tables Create tables if not exist on server
help Show this message
quit exit
--------
Backend option
addr IP address of mysql
port Port of mysql
type read only or read write [rw|ro]
stat status of backend [unknown|up|down|delete|maintaining]
shard_group sharding group name
----
Sharding option
db database name
table table name
key shard column
keytype type of key, [int|str|datetime|...]
method range or hash [range|hash]
partitions detail of sharding
shardnum total number of shard partitions
default_group default sharding group
all_groups all groups list
'''
if __name__ == '__main__':
show_prompt()
try:
while 1:
cmd = raw_input('\033[1;32m>\033[0m ')
cmd = cmd.strip().lower().rstrip(';')
if not cmd:
continue
print_console(run_cmd(cmd))
except EOFError:
print_console_wide('### Good Bye! ###')
except:
traceback.print_exc()
print_console_wide('Error detected! Exit.')
finally:
if conn:
conn.close()