192 lines
5.9 KiB
Python
192 lines
5.9 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
|
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# Copyright 2011 Piston Cloud Computing, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
"""Session management functions."""
|
|
|
|
import os
|
|
import time
|
|
import sqlalchemy
|
|
import logging
|
|
|
|
from migrate.versioning import api as versioning_api
|
|
from migrate import exceptions as versioning_exceptions
|
|
from sqlalchemy.exc import DisconnectionError
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from muranoapi.common.config import CONF as conf
|
|
from muranoapi.db import migrate_repo
|
|
from muranoapi.openstack.common import log as mlogging
|
|
|
|
|
|
_ENGINE = None
|
|
_MAKER = None
|
|
_MAX_RETRIES = None
|
|
_RETRY_INTERVAL = None
|
|
_IDLE_TIMEOUT = None
|
|
_CONNECTION = None
|
|
|
|
sa_logger = None
|
|
log = mlogging.getLogger(__name__)
|
|
|
|
|
|
def _ping_listener(dbapi_conn, connection_rec, connection_proxy):
|
|
|
|
"""
|
|
Ensures that MySQL connections checked out of the
|
|
pool are alive.
|
|
|
|
Borrowed from:
|
|
http://groups.google.com/group/sqlalchemy/msg/a4ce563d802c929f
|
|
"""
|
|
|
|
try:
|
|
dbapi_conn.cursor().execute('select 1')
|
|
except dbapi_conn.OperationalError as ex:
|
|
if ex.args[0] in (2006, 2013, 2014, 2045, 2055):
|
|
msg = 'Got mysql server has gone away: %s' % ex
|
|
log.warn(msg)
|
|
raise DisconnectionError(msg)
|
|
else:
|
|
raise
|
|
|
|
|
|
def setup_db_env():
|
|
"""
|
|
Setup configuration for database
|
|
"""
|
|
global sa_logger, _IDLE_TIMEOUT, _MAX_RETRIES, _RETRY_INTERVAL, _CONNECTION
|
|
|
|
_IDLE_TIMEOUT = conf.sql_idle_timeout
|
|
_MAX_RETRIES = conf.sql_max_retries
|
|
_RETRY_INTERVAL = conf.sql_retry_interval
|
|
_CONNECTION = conf.sql_connection
|
|
sa_logger = logging.getLogger('sqlalchemy.engine')
|
|
if conf.debug:
|
|
sa_logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
def get_session(autocommit=True, expire_on_commit=False):
|
|
"""Helper method to grab session"""
|
|
global _MAKER
|
|
if not _MAKER:
|
|
get_engine()
|
|
_get_maker(autocommit, expire_on_commit)
|
|
assert _MAKER
|
|
session = _MAKER()
|
|
return session
|
|
|
|
|
|
def get_engine():
|
|
"""Return a SQLAlchemy engine."""
|
|
"""May assign _ENGINE if not already assigned"""
|
|
global _ENGINE, sa_logger, _CONNECTION, _IDLE_TIMEOUT, _MAX_RETRIES,\
|
|
_RETRY_INTERVAL
|
|
|
|
if not _ENGINE:
|
|
setup_db_env()
|
|
|
|
connection_dict = sqlalchemy.engine.url.make_url(_CONNECTION)
|
|
|
|
engine_args = {
|
|
'pool_recycle': _IDLE_TIMEOUT,
|
|
'echo': False,
|
|
'convert_unicode': True}
|
|
|
|
try:
|
|
_ENGINE = sqlalchemy.create_engine(_CONNECTION, **engine_args)
|
|
|
|
if 'mysql' in connection_dict.drivername:
|
|
sqlalchemy.event.listen(_ENGINE, 'checkout', _ping_listener)
|
|
|
|
_ENGINE.connect = _wrap_db_error(_ENGINE.connect)
|
|
_ENGINE.connect()
|
|
except Exception as err:
|
|
msg = _("Error configuring registry database with supplied "
|
|
"sql_connection. Got error: %s") % err
|
|
log.error(msg)
|
|
raise
|
|
|
|
if conf.db_auto_create:
|
|
log.info(_('auto-creating DB'))
|
|
_auto_create_db()
|
|
else:
|
|
log.info(_('not auto-creating DB'))
|
|
|
|
return _ENGINE
|
|
|
|
|
|
def _get_maker(autocommit=True, expire_on_commit=False):
|
|
"""Return a SQLAlchemy sessionmaker."""
|
|
"""May assign __MAKER if not already assigned"""
|
|
global _MAKER, _ENGINE
|
|
assert _ENGINE
|
|
if not _MAKER:
|
|
_MAKER = sessionmaker(bind=_ENGINE, autocommit=autocommit,
|
|
expire_on_commit=expire_on_commit)
|
|
return _MAKER
|
|
|
|
|
|
def _is_db_connection_error(args):
|
|
"""Return True if error in connecting to db."""
|
|
# NOTE(adam_g): This is currently MySQL specific and needs to be extended
|
|
# to support Postgres and others.
|
|
conn_err_codes = ('2002', '2003', '2006')
|
|
for err_code in conn_err_codes:
|
|
if args.find(err_code) != -1:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _wrap_db_error(f):
|
|
"""Retry DB connection. Copied from nova and modified."""
|
|
def _wrap(*args, **kwargs):
|
|
try:
|
|
return f(*args, **kwargs)
|
|
except sqlalchemy.exc.OperationalError as e:
|
|
if not _is_db_connection_error(e.args[0]):
|
|
raise
|
|
|
|
remaining_attempts = _MAX_RETRIES
|
|
while True:
|
|
log.warning(_('SQL connection failed. %d attempts left.'),
|
|
remaining_attempts)
|
|
remaining_attempts -= 1
|
|
time.sleep(_RETRY_INTERVAL)
|
|
try:
|
|
return f(*args, **kwargs)
|
|
except sqlalchemy.exc.OperationalError as e:
|
|
if (remaining_attempts == 0 or
|
|
not _is_db_connection_error(e.args[0])):
|
|
raise
|
|
except sqlalchemy.exc.DBAPIError:
|
|
raise
|
|
except sqlalchemy.exc.DBAPIError:
|
|
raise
|
|
_wrap.func_name = f.func_name
|
|
return _wrap
|
|
|
|
|
|
def _auto_create_db():
|
|
repo_path = os.path.abspath(os.path.dirname(migrate_repo.__file__))
|
|
try:
|
|
versioning_api.upgrade(conf.sql_connection, repo_path)
|
|
except versioning_exceptions.DatabaseNotControlledError:
|
|
versioning_api.version_control(conf.sql_connection, repo_path)
|
|
versioning_api.upgrade(conf.sql_connection, repo_path)
|