[API][ADMIN_API] Support MySQL clusters

The API and Admin API server can now connect to multiple servers and
picks one at random on each access.  If one fails SQLAlchemy
automatically tries 3 times to get a new connection.

Change-Id: I961eee18fb402735684e2cfb83fb5dc416e46f87
This commit is contained in:
Andrew Hutchings
2013-07-04 23:16:46 +01:00
parent fd57382e20
commit 00f3220d4b
7 changed files with 130 additions and 213 deletions

View File

@@ -13,13 +13,20 @@ Configuration File
.. code-block:: ini
[admin_api]
db_host=localhost
db_user=root
db_pass=
db_schema=lbaas
db_section=mysql1
ssl_certfile=/opt/server.crt
ssl_keyfile=/opt/server.key
[mysql1]
host=localhost
port=3306
username=root
password=
schema=lbaas
ssl_cert=/opt/mysql_cert.crt
ssl_key=/opt/mysql_key.key
ssl_ca=/opt/mysql_ca.ca
Command Line Options
--------------------
.. program:: libra_admin_api
@@ -32,41 +39,10 @@ Command Line Options
The port number to listen on, default is 8889
.. option:: --db_host <HOSTNAME>
.. option:: --db_secions <SECTIONNAME>
The host name for the MySQL database server
.. option:: --db_port <PORT>
The port number for the MySQL database server
.. option:: --db_user <USERNAME>
The username for the MySQL database server
.. option:: --db_pass <PASSWORD>
The password for the MySQL database server
.. option:: --db_schema <SCHEMA>
The schema containing the LBaaS tables in the MySQL database server
.. option:: --db_ssl
Enable MySQL SSL support
.. option:: --db_ssl_cert <CERTIFICATE PATH>
The path for the MySQL SSL certificate
.. option:: --db_ssl_key <KEY PATH>
The path for the MySQL SSL key
.. option:: --db_ssl_ca <CA PATH>
The path for the MySQL SSL Certificate Authority
Config file sections that describe the MySQL servers. This option can
be specified multiple times for Galera or NDB clusters.
.. option:: --ssl_certfile <PATH>

View File

@@ -13,10 +13,7 @@ Configuration File
.. code-block:: ini
[api]
db_host=localhost
db_user=root
db_pass=
db_schema=lbaas
db_sections=mysql1
gearman=127.0.0.1:4730
keystone_module=keystoneclient.middleware.auth_token:AuthProtocol
swift_basepath=lbaaslogs
@@ -24,6 +21,16 @@ Configuration File
ssl_certfile=/opt/certfile.crt
ssl_keyfile=/opt/keyfile.key
[mysql1]
host=localhost
port=3306
username=root
password=
schema=lbaas
ssl_cert=/opt/mysql_cert.crt
ssl_key=/opt/mysql_key.key
ssl_ca=/opt/mysql_ca.ca
In addition to this any options that are specific to the given keystone
module should be stored in the ``[keystone]`` section.
@@ -43,41 +50,10 @@ Command Line Options
Do not use keystone authentication, for testing purposes only
.. option:: --db_host <HOSTNAME>
.. option:: --db_secions <SECTIONNAME>
The host name for the MySQL database server
.. option:: --db_port <PORT>
The port number for the MySQL database server
.. option:: --db_user <USERNAME>
The username for the MySQL database server
.. option:: --db_pass <PASSWORD>
The password for the MySQL database server
.. option:: --db_schema <SCHEMA>
The schema containing the LBaaS tables in the MySQL database server
.. option:: --db_ssl
Enable MySQL SSL support
.. option:: --db_ssl_cert <CERTIFICATE PATH>
The path for the MySQL SSL certificate
.. option:: --db_ssl_key <KEY PATH>
The path for the MySQL SSL key
.. option:: --db_ssl_ca <CA PATH>
The path for the MySQL SSL Certificate Authority
Config file sections that describe the MySQL servers. This option can
be specified multiple times for Galera or NDB clusters.
.. option:: --gearman <HOST:POST>

View File

@@ -73,9 +73,7 @@ poll_timeout = 5
poll_timeout_retry = 30
[admin_api]
db_user=root
db_pass=passwd
db_schema=lbaas
db_sections=mysql1
ssl_certfile=certfile.crt
ssl_keyfile=keyfile.key
@@ -83,9 +81,7 @@ ssl_keyfile=keyfile.key
host=0.0.0.0
port=8080
disable_keystone=False
db_user=root
db_pass=passwd
db_schema=lbaas
db_sections=mysql1
gearman=127.0.0.1:4730
swift_basepath=lbaaslogs
swift_endpoint=https://host.com:443/v1/
@@ -94,5 +90,11 @@ ssl_keyfile=keyfile.key
expire_days=7
ip_filters=192.168.0.0/24
[mysql1]
username=root
password=
schema=lbaas
host=localhost
# Keystone options go here
[keystone]

View File

@@ -40,18 +40,8 @@ def setup_app(pecan_config, args):
if not pecan_config:
pecan_config = get_pecan_config()
config = dict(pecan_config)
config['database'] = {
'username': args.db_user,
'password': args.db_pass,
'host': args.db_host,
'schema': args.db_schema,
'port': args.db_port,
'schema': args.db_schema,
'use_ssl': args.db_ssl,
'ssl_cert': args.db_ssl_cert,
'ssl_key': args.db_ssl_key,
'ssl_ca': args.db_ssl_ca
}
config['database'] = args.db_sections
config['conffile'] = args.config
if args.debug:
config['wsme'] = {'debug': True}
config['app']['debug'] = True
@@ -92,31 +82,8 @@ def main():
'--port', help='Port number for API server', type=int, default=8889
)
options.parser.add_argument(
'--db_user', help='MySQL database user'
)
options.parser.add_argument(
'--db_pass', help='MySQL database password'
)
options.parser.add_argument(
'--db_host', help='MySQL host name'
)
options.parser.add_argument(
'--db_port', help='MySQL port number', default=3306, type=int
)
options.parser.add_argument(
'--db_schema', help='MySQL schema for libra'
)
options.parser.add_argument(
'--db_ssl', help='Enable MySQL SSL connections', action='store_true'
)
options.parser.add_argument(
'--db_ssl_cert', help='MySQL SSL certificate'
)
options.parser.add_argument(
'--db_ssl_key', help='MySQL SSL key'
)
options.parser.add_argument(
'--db_ssl_ca', help='MySQL SSL certificate authority'
'--db_sections', action='append', default=[],
help='MySQL config sections in the config file'
)
options.parser.add_argument(
'--ssl_certfile',
@@ -129,12 +96,7 @@ def main():
args = options.run()
required_args = [
'db_user', 'db_pass', 'db_host', 'db_schema', 'ssl_certfile',
'ssl_keyfile'
]
if args.db_ssl:
required_args.extend(['db_ssl_cert', 'db_ssl_key', 'db_ssl_ca'])
required_args = ['db_sections', 'ssl_certfile', 'ssl_keyfile']
missing_args = 0
for req in required_args:
@@ -147,6 +109,10 @@ def main():
if missing_args:
return 2
if not isinstance(args.db_sections, list):
db_list = args.db_sections.split()
args.db_sections = db_list
pc = get_pecan_config()
if not args.nodaemon:
pidfile = daemon.pidfile.TimeoutPIDLockFile(args.pid, 10)

View File

@@ -15,38 +15,47 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, create_engine
from sqlalchemy import INTEGER, VARCHAR, BIGINT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy.orm import relationship, backref, sessionmaker, Session
import sqlalchemy.types as types
import random
import ConfigParser
from pecan import conf
# TODO replace this with something better
conn_string = '''mysql://%s:%s@%s:%d/%s''' % (
conf.database.username,
conf.database.password,
conf.database.host,
conf.database.port,
conf.database.schema
)
if conf.database.use_ssl:
ssl_args = {'ssl': {
'cert': conf.database.ssl_cert,
'key': conf.database.ssl_key,
'ca': conf.database.ssl_ca
}}
config = ConfigParser.SafeConfigParser()
config.read([conf.conffile])
engines = []
for section in conf.database:
db_conf = config._sections[section]
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", connect_args=ssl_args,
pool_recycle=3600
)
else:
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_recycle=3600
conn_string = '''mysql://%s:%s@%s:%d/%s''' % (
db_conf['username'],
db_conf['password'],
db_conf['host'],
db_conf.get('port', 3306),
db_conf['schema']
)
if 'ssl_key' in db_conf:
ssl_args = {'ssl': {
'cert': db_conf['ssl_cert'],
'key': db_conf['ssl_key'],
'ca': db_conf['ssl_ca']
}}
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_size=20,
connect_args=ssl_args, pool_recycle=3600
)
else:
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_size=20,
pool_recycle=3600
)
engines.append(engine)
DeclarativeBase = declarative_base()
metadata = DeclarativeBase.metadata
metadata.bind = engine
loadbalancers_devices = Table(
'loadbalancers_devices',
@@ -129,12 +138,17 @@ class Node(DeclarativeBase):
weight = Column(u'weight', INTEGER(), nullable=False)
class RoutingSession(Session):
def get_bind(self, mapper=None, clause=None):
return random.choice(engines)
class db_session(object):
def __init__(self):
self.session = None
def __enter__(self):
self.session = sessionmaker(bind=engine)()
self.session = sessionmaker(bind=engine, class_=RoutingSession)()
return self.session
def __exit__(self, type, value, traceback):

View File

@@ -46,17 +46,8 @@ def setup_app(pecan_config, args):
if not pecan_config:
pecan_config = get_pecan_config()
config = dict(pecan_config)
config['database'] = {
'username': args.db_user,
'password': args.db_pass,
'host': args.db_host,
'port': args.db_port,
'schema': args.db_schema,
'use_ssl': args.db_ssl,
'ssl_cert': args.db_ssl_cert,
'ssl_key': args.db_ssl_key,
'ssl_ca': args.db_ssl_ca
}
config['database'] = args.db_sections
config['conffile'] = args.config
config['swift'] = {
'swift_basepath': args.swift_basepath,
'swift_endpoint': args.swift_endpoint
@@ -115,31 +106,8 @@ def main():
action='store_true'
)
options.parser.add_argument(
'--db_user', help='MySQL database user'
)
options.parser.add_argument(
'--db_pass', help='MySQL database password'
)
options.parser.add_argument(
'--db_host', help='MySQL host name'
)
options.parser.add_argument(
'--db_port', help='MySQL port number', default=3306, type=int
)
options.parser.add_argument(
'--db_schema', help='MySQL schema for libra'
)
options.parser.add_argument(
'--db_ssl', help='Enable MySQL SSL connections', action='store_true'
)
options.parser.add_argument(
'--db_ssl_cert', help='MySQL SSL certificate'
)
options.parser.add_argument(
'--db_ssl_key', help='MySQL SSL key'
)
options.parser.add_argument(
'--db_ssl_ca', help='MySQL SSL certificate authority'
'--db_sections', action='append', default=[],
help='MySQL config sections in the config file'
)
options.parser.add_argument(
'--gearman', action='append', metavar='HOST:PORT', default=[],
@@ -178,11 +146,9 @@ def main():
args = options.run()
required_args = [
'db_user', 'db_pass', 'db_host', 'db_schema', 'swift_basepath',
'db_sections', 'swift_basepath',
'swift_endpoint', 'ssl_certfile', 'ssl_keyfile'
]
if args.db_ssl:
required_args.extend(['db_ssl_cert', 'db_ssl_key', 'db_ssl_ca'])
missing_args = 0
for req in required_args:
@@ -206,6 +172,10 @@ def main():
svr_list = args.gearman.split()
args.gearman = svr_list
if not isinstance(args.db_sections, list):
db_list = args.db_sections.split()
args.db_sections = db_list
if not isinstance(args.ip_filters, list):
ip_list = args.ip_filters.split()
args.ip_filters = ip_list

View File

@@ -15,39 +15,47 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, create_engine
from sqlalchemy import INTEGER, VARCHAR, BIGINT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy.orm import relationship, backref, sessionmaker, Session
import sqlalchemy.types as types
import random
import ConfigParser
from pecan import conf
# TODO replace this with something better
conn_string = '''mysql://%s:%s@%s:%d/%s''' % (
conf.database.username,
conf.database.password,
conf.database.host,
conf.database.port,
conf.database.schema
)
if conf.database.use_ssl:
ssl_args = {'ssl': {
'cert': conf.database.ssl_cert,
'key': conf.database.ssl_key,
'ca': conf.database.ssl_ca
}}
config = ConfigParser.SafeConfigParser()
config.read([conf.conffile])
engines = []
for section in conf.database:
db_conf = config._sections[section]
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_size=20,
connect_args=ssl_args, pool_recycle=3600
)
else:
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_size=20,
pool_recycle=3600
conn_string = '''mysql://%s:%s@%s:%d/%s''' % (
db_conf['username'],
db_conf['password'],
db_conf['host'],
db_conf.get('port', 3306),
db_conf['schema']
)
if 'ssl_key' in db_conf:
ssl_args = {'ssl': {
'cert': db_conf['ssl_cert'],
'key': db_conf['ssl_key'],
'ca': db_conf['ssl_ca']
}}
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_size=20,
connect_args=ssl_args, pool_recycle=3600
)
else:
engine = create_engine(
conn_string, isolation_level="READ COMMITTED", pool_size=20,
pool_recycle=3600
)
engines.append(engine)
DeclarativeBase = declarative_base()
metadata = DeclarativeBase.metadata
metadata.bind = engine
loadbalancers_devices = Table(
'loadbalancers_devices',
@@ -130,12 +138,17 @@ class Node(DeclarativeBase):
weight = Column(u'weight', INTEGER(), nullable=False)
class RoutingSession(Session):
def get_bind(self, mapper=None, clause=None):
return random.choice(engines)
class db_session(object):
def __init__(self):
self.session = None
def __enter__(self):
self.session = sessionmaker(bind=engine)()
self.session = sessionmaker(bind=engine, class_=RoutingSession)()
return self.session
def __exit__(self, type, value, traceback):