Partial fix for bug #1074093

engine and makers are stored in dictionnary in the
sqlalchemy/session.py

Change-Id: Ied2c13e9ed7117730eccc8db4c3a03b54c66d4b9
This commit is contained in:
JC Martin 2012-12-03 14:04:52 -08:00 committed by Kiall Mac Innes
parent c8ea8fc554
commit f8df098d30
9 changed files with 79 additions and 75 deletions

View File

@ -25,6 +25,7 @@ from moniker.central import api as central_api
from moniker.context import MonikerContext
from sqlalchemy.ext.sqlsoup import SqlSoup
from sqlalchemy.engine.url import _parse_rfc1738_args
from moniker.sqlalchemy.session import get_engine
LOG = logging.getLogger(__name__)
@ -73,7 +74,8 @@ class MySQLBind9Backend(base.Backend):
super(MySQLBind9Backend, self).start()
if cfg.CONF[self.name].write_database:
self._db = SqlSoup(cfg.CONF[self.name].database_connection)
self._engine = get_engine(self.name)
self._db = SqlSoup(self._engine)
self._sync_domains()

View File

@ -30,6 +30,8 @@ HANDLER_NAMESPACE = 'moniker.notification.handler'
cfg.CONF.register_opts([
cfg.StrOpt('backend-driver', default='rpc',
help='The backend driver to use'),
cfg.StrOpt('storage-driver', default='sqlalchemy',
help='The storage driver to use'),
cfg.ListOpt('enabled-notification-handlers', default=[],
help='Enabled Notification Handlers'),
])

View File

@ -30,19 +30,25 @@ from moniker.openstack.common.gettextutils import _
LOG = logging.getLogger(__name__)
_MAKER = None
_ENGINE = None
_MAKERS = {}
_ENGINES = {}
def get_session(autocommit=True, expire_on_commit=False, autoflush=True):
def get_session(config_group,
autocommit=True,
expire_on_commit=False,
autoflush=True):
"""Return a SQLAlchemy session."""
global _MAKER
global _MAKERS
if _MAKER is None:
engine = get_engine()
_MAKER = get_maker(engine, autocommit, expire_on_commit, autoflush)
if config_group not in _MAKERS:
engine = get_engine(config_group)
_MAKERS[config_group] = get_maker(engine,
autocommit,
expire_on_commit,
autoflush)
session = _MAKER()
session = _MAKERS[config_group]()
return session
@ -89,56 +95,64 @@ def is_db_connection_error(args):
return False
def get_engine():
def get_engine(config_group):
"""Return a SQLAlchemy engine."""
global _ENGINE
if _ENGINE is None:
global _ENGINES
database_connection = cfg.CONF[config_group].database_connection
if config_group not in _ENGINES:
connection_dict = sqlalchemy.engine.url.make_url(
cfg.CONF.database_connection)
database_connection)
engine_args = {
"pool_recycle": cfg.CONF['storage:sqlalchemy'].idle_timeout,
"pool_recycle": cfg.CONF[config_group].idle_timeout,
"echo": False,
'convert_unicode': True,
}
# Map our SQL debug level to SQLAlchemy's options
if cfg.CONF['storage:sqlalchemy'].connection_debug >= 100:
if cfg.CONF[config_group].connection_debug >= 100:
engine_args['echo'] = 'debug'
elif cfg.CONF['storage:sqlalchemy'].connection_debug >= 50:
elif cfg.CONF[config_group].connection_debug >= 50:
engine_args['echo'] = True
if "sqlite" in connection_dict.drivername:
engine_args["poolclass"] = NullPool
if cfg.CONF.database_connection == "sqlite://":
if database_connection == "sqlite://":
engine_args["poolclass"] = StaticPool
engine_args["connect_args"] = {'check_same_thread': False}
_ENGINE = sqlalchemy.create_engine(cfg.CONF.database_connection,
**engine_args)
_ENGINES[config_group] = sqlalchemy.create_engine(database_connection,
**engine_args)
if 'mysql' in connection_dict.drivername:
sqlalchemy.event.listen(_ENGINE, 'checkout', ping_listener)
sqlalchemy.event.listen(_ENGINES[config_group],
'checkout',
ping_listener)
elif "sqlite" in connection_dict.drivername:
if not cfg.CONF['storage:sqlalchemy'].sqlite_synchronous:
sqlalchemy.event.listen(_ENGINE, 'connect',
if not cfg.CONF[config_group].sqlite_synchronous:
sqlalchemy.event.listen(_ENGINES[config_group],
'connect',
synchronous_switch_listener)
sqlalchemy.event.listen(_ENGINE, 'connect', add_regexp_listener)
sqlalchemy.event.listen(_ENGINES[config_group],
'connect',
add_regexp_listener)
if (cfg.CONF['storage:sqlalchemy'].connection_trace and
_ENGINE.dialect.dbapi.__name__ == 'MySQLdb'):
if (cfg.CONF[config_group].connection_trace and
_ENGINES[config_group].dialect.dbapi.__name__ == 'MySQLdb'):
import MySQLdb.cursors
_do_query = debug_mysql_do_query()
setattr(MySQLdb.cursors.BaseCursor, '_do_query', _do_query)
try:
_ENGINE.connect()
_ENGINES[config_group].connect()
except OperationalError, e:
if not is_db_connection_error(e.args[0]):
raise
remaining = cfg.CONF['storage:sqlalchemy'].max_retries
remaining = cfg.CONF[config_group].max_retries
if remaining == -1:
remaining = 'infinite'
while True:
@ -146,15 +160,15 @@ def get_engine():
LOG.warn(msg % remaining)
if remaining != 'infinite':
remaining -= 1
time.sleep(cfg.CONF['storage:sqlalchemy'].retry_interval)
time.sleep(cfg.CONF[config_group].retry_interval)
try:
_ENGINE.connect()
_ENGINES[config_group].connect()
break
except OperationalError, e:
if (remaining != 'infinite' and remaining == 0) or \
not is_db_connection_error(e.args[0]):
raise
return _ENGINE
return _ENGINES[config_group]
def get_maker(engine, autocommit=True, expire_on_commit=False, autoflush=True):

View File

@ -13,19 +13,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from urlparse import urlparse
from moniker.openstack.common import cfg
from moniker.openstack.common import log as logging
from moniker.storage.base import StorageEngine
LOG = logging.getLogger(__name__)
cfg.CONF.register_opts([
cfg.StrOpt('database-connection',
default='sqlite:///$state_path/moniker.sqlite',
help='The database driver to use')
])
def get_engine_name(string):
"""
@ -34,15 +27,15 @@ def get_engine_name(string):
return string.split("+")[0]
def get_engine():
scheme = urlparse(cfg.CONF.database_connection).scheme
engine_name = get_engine_name(scheme)
return StorageEngine.get_plugin(
engine_name, invoke_on_load=True)
def get_engine(engine_name):
"""
Return the engine class from the provided engine name
"""
return StorageEngine.get_plugin(engine_name, invoke_on_load=True)
def get_connection():
engine = get_engine()
engine = get_engine(cfg.CONF.storage_driver)
return engine.get_connection()

View File

@ -21,10 +21,12 @@ from moniker.storage import base
from moniker.storage.impl_sqlalchemy import models
from moniker.sqlalchemy.session import get_session
LOG = logging.getLogger(__name__)
SQL_OPTS = [
cfg.StrOpt('database_connection',
default='sqlite:///$state_path/moniker.sqlite',
help='The database driver to use'),
cfg.IntOpt('connection_debug', default=0,
help='Verbosity of SQL debugging information. 0=None,'
' 100=Everything'),
@ -52,22 +54,21 @@ class SQLAlchemyStorage(base.StorageEngine):
return opts
def get_connection(self):
return Connection()
return Connection(self.name)
class Connection(base.Connection):
"""
SQLAlchemy connection
"""
def __init__(self):
LOG.info('connecting to %s', cfg.CONF.database_connection)
self.session = self._get_connection()
def __init__(self, config_group):
self.session = self._get_connection(config_group)
def _get_connection(self):
def _get_connection(self, config_group):
"""
Return a connection to the database.
"""
return get_session()
return get_session(config_group)
def setup_schema(self):
""" Semi-Private Method to create the database schema """

View File

@ -15,14 +15,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from urlparse import urlparse
from sqlalchemy import (Column, DateTime, String, Text, Integer, ForeignKey,
Enum, Boolean, Unicode)
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship, backref, object_mapper
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from moniker.openstack.common import cfg
from moniker.openstack.common import log as logging
from moniker.openstack.common import timeutils
from moniker.openstack.common.uuidutils import generate_uuid
@ -31,24 +29,12 @@ from moniker.sqlalchemy.types import UUID, Inet
LOG = logging.getLogger(__name__)
sql_opts = [
cfg.IntOpt('mysql_engine', default='InnoDB', help='MySQL engine')
]
cfg.CONF.register_opts(sql_opts)
RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'NS']
def table_args():
engine_name = urlparse(cfg.CONF.database_connection).scheme
if engine_name == 'mysql':
return {'mysql_engine': cfg.CONF.mysql_engine}
return None
class Base(object):
__abstract__ = True
__table_initialized__ = False
id = Column(UUID, default=generate_uuid, primary_key=True)
@ -60,9 +46,6 @@ class Base(object):
'version_id_col': version
}
__table_args__ = table_args()
__table_initialized__ = False
def save(self, session):
""" Save this object """
session.add(self)

View File

@ -25,6 +25,10 @@ from moniker.central import service as central_service
LOG = logging.getLogger(__name__)
# NOTE(kiall): Awful kludge, to be removed in the next patchset
from moniker.storage.impl_sqlalchemy import SQLAlchemyStorage
SQLAlchemyStorage.register_opts()
class AssertMixin(object):
"""
@ -96,12 +100,17 @@ class TestCase(unittest2.TestCase, AssertMixin):
self.mox = mox.Mox()
self.config(
database_connection='sqlite://',
rpc_backend='moniker.openstack.common.rpc.impl_fake',
notification_driver=[],
storage_driver='sqlalchemy',
backend_driver='fake',
notification_driver=[],
rpc_backend='moniker.openstack.common.rpc.impl_fake',
auth_strategy='noauth'
)
self.config(
database_connection='sqlite://',
group='storage:sqlalchemy'
)
storage.setup_schema()
self.admin_context = self.get_admin_context()
@ -114,6 +123,7 @@ class TestCase(unittest2.TestCase, AssertMixin):
def config(self, **kwargs):
group = kwargs.pop('group', None)
for k, v in kwargs.iteritems():
cfg.CONF.set_override(k, v, group)

View File

@ -24,4 +24,5 @@ class SqlalchemyStorageTest(StorageDriverTestCase):
def setUp(self):
super(SqlalchemyStorageTest, self).setUp()
self.config(database_connection='sqlite://')
self.config(database_connection='sqlite://',
group='storage:sqlalchemy')

View File

@ -57,9 +57,7 @@ setup(
cmdclass=common_setup.get_cmdclass(),
entry_points=textwrap.dedent("""
[moniker.storage]
mysql = moniker.storage.impl_sqlalchemy:SQLAlchemyStorage
postgresql = moniker.storage.impl_sqlalchemy:SQLAlchemyStorage
sqlite = moniker.storage.impl_sqlalchemy:SQLAlchemyStorage
sqlalchemy = moniker.storage.impl_sqlalchemy:SQLAlchemyStorage
[moniker.notification.handler]
nova_fixed = moniker.notification_handler.nova:NovaFixedHandler