Use oslo database code

Bring in the new database code from oslo.

Uses get_session() from oslo as well as changing NovaBase to derive from
a common class.

Remove test_sqlalchemy.py now that this code is test in oslo.

Implements blueprint db-common.

Change-Id: I090754981c871250dd981cbbe1a08e7181440120
This commit is contained in:
Eric Windisch 2013-01-17 15:38:02 -05:00 committed by Mark McLoughlin
parent 47bbf12a6c
commit cd0c765ced
24 changed files with 263 additions and 264 deletions

View File

@ -80,6 +80,7 @@ from nova.db import migration
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common import cliutils from nova.openstack.common import cliutils
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import importutils from nova.openstack.common import importutils
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.openstack.common import rpc from nova.openstack.common import rpc
@ -831,7 +832,7 @@ class InstanceTypeCommands(object):
except exception.InstanceTypeNotFound: except exception.InstanceTypeNotFound:
print _("Valid instance type name is required") print _("Valid instance type name is required")
sys.exit(1) sys.exit(1)
except exception.DBError, e: except db_session.DBError, e:
print _("DB Error: %s") % e print _("DB Error: %s") % e
sys.exit(2) sys.exit(2)
except Exception: except Exception:
@ -848,7 +849,7 @@ class InstanceTypeCommands(object):
inst_types = instance_types.get_all_types() inst_types = instance_types.get_all_types()
else: else:
inst_types = instance_types.get_instance_type_by_name(name) inst_types = instance_types.get_instance_type_by_name(name)
except exception.DBError, e: except db_session.DBError, e:
_db_error(e) _db_error(e)
if isinstance(inst_types.values()[0], dict): if isinstance(inst_types.values()[0], dict):
for k, v in inst_types.iteritems(): for k, v in inst_types.iteritems():
@ -879,7 +880,7 @@ class InstanceTypeCommands(object):
ext_spec) ext_spec)
print _("Key %(key)s set to %(value)s on instance" print _("Key %(key)s set to %(value)s on instance"
" type %(name)s") % locals() " type %(name)s") % locals()
except exception.DBError, e: except db_session.DBError, e:
_db_error(e) _db_error(e)
@args('--name', dest='name', metavar='<name>', @args('--name', dest='name', metavar='<name>',
@ -902,7 +903,7 @@ class InstanceTypeCommands(object):
key) key)
print _("Key %(key)s on instance type %(name)s unset") % locals() print _("Key %(key)s on instance type %(name)s unset") % locals()
except exception.DBError, e: except db_session.DBError, e:
_db_error(e) _db_error(e)

View File

@ -27,6 +27,7 @@ from nova import context
from nova import db from nova import db
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova import utils from nova import utils
@ -110,7 +111,7 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=None, flavorid=None,
try: try:
return db.instance_type_create(context.get_admin_context(), kwargs) return db.instance_type_create(context.get_admin_context(), kwargs)
except exception.DBError, e: except db_session.DBError, e:
LOG.exception(_('DB error: %s') % e) LOG.exception(_('DB error: %s') % e)
raise exception.InstanceTypeCreateFailed() raise exception.InstanceTypeCreateFailed()

View File

@ -18,10 +18,16 @@
# under the License. # under the License.
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import rpc from nova.openstack.common import rpc
from nova import paths
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('$sqlite_db')
def parse_args(argv, default_config_files=None): def parse_args(argv, default_config_files=None):
db_session.set_defaults(sql_connection=_DEFAULT_SQL_CONNECTION,
sqlite_db='nova.sqlite')
rpc.set_defaults(control_exchange='nova') rpc.set_defaults(control_exchange='nova')
cfg.CONF(argv[1:], cfg.CONF(argv[1:],
project='nova', project='nova',

View File

@ -35,14 +35,14 @@ from sqlalchemy.sql.expression import desc
from sqlalchemy.sql import func from sqlalchemy.sql import func
from nova import block_device from nova import block_device
from nova.common import sqlalchemyutils
from nova.compute import task_states from nova.compute import task_states
from nova.compute import vm_states from nova.compute import vm_states
from nova import db from nova import db
from nova.db.sqlalchemy import models from nova.db.sqlalchemy import models
from nova.db.sqlalchemy.session import get_session
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common.db.sqlalchemy import utils as sqlalchemyutils
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.openstack.common import timeutils from nova.openstack.common import timeutils
from nova.openstack.common import uuidutils from nova.openstack.common import uuidutils
@ -58,10 +58,13 @@ db_opts = [
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(db_opts) CONF.register_opts(db_opts)
CONF.import_opt('compute_topic', 'nova.compute.rpcapi') CONF.import_opt('compute_topic', 'nova.compute.rpcapi')
CONF.import_opt('sql_connection', 'nova.db.sqlalchemy.session') CONF.import_opt('sql_connection',
'nova.openstack.common.db.sqlalchemy.session')
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
get_session = db_session.get_session
def is_user_context(context): def is_user_context(context):
"""Indicates if the request context is a normal user.""" """Indicates if the request context is a normal user."""
@ -1251,7 +1254,7 @@ def virtual_interface_create(context, values):
vif_ref = models.VirtualInterface() vif_ref = models.VirtualInterface()
vif_ref.update(values) vif_ref.update(values)
vif_ref.save() vif_ref.save()
except exception.DBError: except db_session.DBError:
raise exception.VirtualInterfaceCreateException() raise exception.VirtualInterfaceCreateException()
return vif_ref return vif_ref
@ -3535,7 +3538,7 @@ def instance_type_create(context, values):
instance_type_ref.update(values) instance_type_ref.update(values)
instance_type_ref.save(session=session) instance_type_ref.save(session=session)
except Exception, e: except Exception, e:
raise exception.DBError(e) raise db_session.DBError(e)
return _dict_with_extra_specs(instance_type_ref) return _dict_with_extra_specs(instance_type_ref)
@ -4238,7 +4241,7 @@ def s3_image_create(context, image_uuid):
s3_image_ref.update({'uuid': image_uuid}) s3_image_ref.update({'uuid': image_uuid})
s3_image_ref.save() s3_image_ref.save()
except Exception, e: except Exception, e:
raise exception.DBError(e) raise db_session.DBError(e)
return s3_image_ref return s3_image_ref

View File

@ -20,8 +20,8 @@ import distutils.version as dist_version
import os import os
from nova.db import migration from nova.db import migration
from nova.db.sqlalchemy.session import get_engine
from nova import exception from nova import exception
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
@ -62,6 +62,8 @@ from migrate.versioning.repository import Repository
_REPOSITORY = None _REPOSITORY = None
get_engine = db_session.get_engine
def db_sync(version=None): def db_sync(version=None):
if version is not None: if version is not None:

View File

@ -26,9 +26,9 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float
from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy.orm import relationship, backref, object_mapper
from nova.db.sqlalchemy.session import get_session
from nova.db.sqlalchemy import types from nova.db.sqlalchemy import types
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import models
from nova.openstack.common import timeutils from nova.openstack.common import timeutils
@ -36,74 +36,8 @@ CONF = cfg.CONF
BASE = declarative_base() BASE = declarative_base()
class NovaBase(object): class NovaBase(models.SoftDeleteMixin, models.ModelBase):
"""Base class for Nova Models.""" pass
__table_initialized__ = False
created_at = Column(DateTime, default=timeutils.utcnow)
updated_at = Column(DateTime, onupdate=timeutils.utcnow)
deleted_at = Column(DateTime)
deleted = Column(Integer, default=0)
metadata = None
def save(self, session=None):
"""Save this object."""
if not session:
session = get_session()
# NOTE(boris-42): This part of code should be look like:
# sesssion.add(self)
# session.flush()
# But there is a bug in sqlalchemy and eventlet that
# raises NoneType exception if there is no running
# transaction and rollback is called. As long as
# sqlalchemy has this bug we have to create transaction
# explicity.
with session.begin(subtransactions=True):
session.add(self)
session.flush()
def soft_delete(self, session=None):
"""Mark this object as deleted."""
self.deleted = self.id
self.deleted_at = timeutils.utcnow()
self.save(session=session)
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key)
def get(self, key, default=None):
return getattr(self, key, default)
def __iter__(self):
columns = dict(object_mapper(self).columns).keys()
# NOTE(russellb): Allow models to specify other keys that can be looked
# up, beyond the actual db columns. An example would be the 'name'
# property for an Instance.
if hasattr(self, '_extra_keys'):
columns.extend(self._extra_keys())
self._i = iter(columns)
return self
def next(self):
n = self._i.next()
return n, getattr(self, n)
def update(self, values):
"""Make the model object behave like a dict."""
for k, v in values.iteritems():
setattr(self, k, v)
def iteritems(self):
"""Make the model object behave like a dict.
Includes attributes from joins."""
local = dict(self)
joined = dict([(k, v) for k, v in self.__dict__.iteritems()
if not k[0] == '_'])
local.update(joined)
return local.iteritems()
class Service(BASE, NovaBase): class Service(BASE, NovaBase):

View File

@ -164,20 +164,6 @@ class EC2APIError(NovaException):
super(EC2APIError, self).__init__(outstr) super(EC2APIError, self).__init__(outstr)
class DBError(NovaException):
"""Wraps an implementation specific exception."""
def __init__(self, inner_exception=None):
self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception))
class DBDuplicateEntry(DBError):
"""Wraps an implementation specific exception."""
def __init__(self, columns=[], inner_exception=None):
self.columns = columns
super(DBDuplicateEntry, self).__init__(inner_exception)
class EncryptionFailure(NovaException): class EncryptionFailure(NovaException):
message = _("Failed to encrypt text: %(reason)s") message = _("Failed to encrypt text: %(reason)s")

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Cloudscaling Group, 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.

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Cloudscaling Group, 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.

View File

@ -0,0 +1,103 @@
# 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.
# Copyright 2012 Cloudscaling Group, 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.
"""
SQLAlchemy models.
"""
from sqlalchemy import Column, Integer
from sqlalchemy import DateTime
from sqlalchemy.orm import object_mapper
from nova.openstack.common.db.sqlalchemy.session import get_session
from nova.openstack.common import timeutils
class ModelBase(object):
"""Base class for models."""
__table_initialized__ = False
created_at = Column(DateTime, default=timeutils.utcnow)
updated_at = Column(DateTime, onupdate=timeutils.utcnow)
metadata = None
def save(self, session=None):
"""Save this object."""
if not session:
session = get_session()
# NOTE(boris-42): This part of code should be look like:
# sesssion.add(self)
# session.flush()
# But there is a bug in sqlalchemy and eventlet that
# raises NoneType exception if there is no running
# transaction and rollback is called. As long as
# sqlalchemy has this bug we have to create transaction
# explicity.
with session.begin(subtransactions=True):
session.add(self)
session.flush()
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key)
def get(self, key, default=None):
return getattr(self, key, default)
def __iter__(self):
columns = dict(object_mapper(self).columns).keys()
# NOTE(russellb): Allow models to specify other keys that can be looked
# up, beyond the actual db columns. An example would be the 'name'
# property for an Instance.
if hasattr(self, '_extra_keys'):
columns.extend(self._extra_keys())
self._i = iter(columns)
return self
def next(self):
n = self._i.next()
return n, getattr(self, n)
def update(self, values):
"""Make the model object behave like a dict."""
for k, v in values.iteritems():
setattr(self, k, v)
def iteritems(self):
"""Make the model object behave like a dict.
Includes attributes from joins."""
local = dict(self)
joined = dict([(k, v) for k, v in self.__dict__.iteritems()
if not k[0] == '_'])
local.update(joined)
return local.iteritems()
class SoftDeleteMixin(object):
deleted_at = Column(DateTime)
deleted = Column(Integer, default=0)
def soft_delete(self, session=None):
"""Mark this object as deleted."""
self.deleted = self.id
self.deleted_at = timeutils.utcnow()
self.save(session=session)

View File

@ -18,6 +18,16 @@
"""Session Handling for SQLAlchemy backend. """Session Handling for SQLAlchemy backend.
Initializing:
* Call set_defaults with the minimal of the following kwargs:
sql_connection, sqlite_db
Example:
session.set_defaults(sql_connection="sqlite:///var/lib/nova/sqlite.db",
sqlite_db="/var/lib/nova/sqlite.db")
Recommended ways to use sessions within this framework: Recommended ways to use sessions within this framework:
* Don't use them explicitly; this is like running with AUTOCOMMIT=1. * Don't use them explicitly; this is like running with AUTOCOMMIT=1.
@ -159,6 +169,15 @@ There are some things which it is best to avoid:
proper UNIQUE constraints are added to the tables. proper UNIQUE constraints are added to the tables.
Enabling soft deletes:
* To use/enable soft-deletes, the SoftDeleteMixin must be added
to your model class. For example:
class NovaBase(models.SoftDeleteMixin, models.ModelBase):
pass
Efficient use of soft deletes: Efficient use of soft deletes:
* There are two possible ways to mark a record as deleted: * There are two possible ways to mark a record as deleted:
@ -221,6 +240,7 @@ Efficient use of soft deletes:
# This will produce count(bar_refs) db requests. # This will produce count(bar_refs) db requests.
""" """
import os.path
import re import re
import time import time
@ -238,16 +258,17 @@ import sqlalchemy.orm
from sqlalchemy.pool import NullPool, StaticPool from sqlalchemy.pool import NullPool, StaticPool
from sqlalchemy.sql.expression import literal_column from sqlalchemy.sql.expression import literal_column
import nova.exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
import nova.openstack.common.log as logging from nova.openstack.common import log as logging
from nova.openstack.common.gettextutils import _
from nova.openstack.common import timeutils from nova.openstack.common import timeutils
from nova import paths
sql_opts = [ sql_opts = [
cfg.StrOpt('sql_connection', cfg.StrOpt('sql_connection',
default='sqlite:///' + paths.state_path_def('$sqlite_db'), default='sqlite:///' +
os.path.abspath(os.path.join(os.path.dirname(__file__),
'../', '$sqlite_db')),
help='The SQLAlchemy connection string used to connect to the ' help='The SQLAlchemy connection string used to connect to the '
'database'), 'database'),
cfg.StrOpt('sqlite_db', cfg.StrOpt('sqlite_db',
@ -262,11 +283,11 @@ sql_opts = [
cfg.IntOpt('sql_min_pool_size', cfg.IntOpt('sql_min_pool_size',
default=1, default=1,
help='Minimum number of SQL connections to keep open in a ' help='Minimum number of SQL connections to keep open in a '
'pool'), 'pool'),
cfg.IntOpt('sql_max_pool_size', cfg.IntOpt('sql_max_pool_size',
default=5, default=5,
help='Maximum number of SQL connections to keep open in a ' help='Maximum number of SQL connections to keep open in a '
'pool'), 'pool'),
cfg.IntOpt('sql_max_retries', cfg.IntOpt('sql_max_retries',
default=10, default=10,
help='maximum db connection retries during startup. ' help='maximum db connection retries during startup. '
@ -297,6 +318,13 @@ _ENGINE = None
_MAKER = None _MAKER = None
def set_defaults(sql_connection, sqlite_db):
"""Set defaults for configuration variables."""
cfg.set_defaults(sql_opts,
sql_connection=sql_connection,
sqlite_db=sqlite_db)
def get_session(autocommit=True, expire_on_commit=False): def get_session(autocommit=True, expire_on_commit=False):
"""Return a SQLAlchemy session.""" """Return a SQLAlchemy session."""
global _MAKER global _MAKER
@ -309,6 +337,25 @@ def get_session(autocommit=True, expire_on_commit=False):
return session return session
class DBError(Exception):
"""Wraps an implementation specific exception."""
def __init__(self, inner_exception=None):
self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception))
class DBDuplicateEntry(DBError):
"""Wraps an implementation specific exception."""
def __init__(self, columns=[], inner_exception=None):
self.columns = columns
super(DBDuplicateEntry, self).__init__(inner_exception)
class InvalidUnicodeParameter(Exception):
message = _("Invalid Parameter: "
"Unicode is not supported by the current database.")
# note(boris-42): In current versions of DB backends unique constraint # note(boris-42): In current versions of DB backends unique constraint
# violation messages follow the structure: # violation messages follow the structure:
# #
@ -362,7 +409,7 @@ def raise_if_duplicate_entry_error(integrity_error, engine_name):
columns = columns.strip().split(", ") columns = columns.strip().split(", ")
else: else:
columns = get_columns_from_uniq_cons_or_name(columns) columns = get_columns_from_uniq_cons_or_name(columns)
raise nova.exception.DBDuplicateEntry(columns, integrity_error) raise DBDuplicateEntry(columns, integrity_error)
def wrap_db_error(f): def wrap_db_error(f):
@ -370,7 +417,7 @@ def wrap_db_error(f):
try: try:
return f(*args, **kwargs) return f(*args, **kwargs)
except UnicodeEncodeError: except UnicodeEncodeError:
raise nova.exception.InvalidUnicodeParameter() raise InvalidUnicodeParameter()
# note(boris-42): We should catch unique constraint violation and # note(boris-42): We should catch unique constraint violation and
# wrap it by our own DBDuplicateEntry exception. Unique constraint # wrap it by our own DBDuplicateEntry exception. Unique constraint
# violation is wrapped by IntegrityError. # violation is wrapped by IntegrityError.
@ -381,10 +428,10 @@ def wrap_db_error(f):
# means we should get names of columns, which values violate # means we should get names of columns, which values violate
# unique constraint, from error message. # unique constraint, from error message.
raise_if_duplicate_entry_error(e, get_engine().name) raise_if_duplicate_entry_error(e, get_engine().name)
raise nova.exception.DBError(e) raise DBError(e)
except Exception, e: except Exception, e:
LOG.exception(_('DB exception wrapped.')) LOG.exception(_('DB exception wrapped.'))
raise nova.exception.DBError(e) raise DBError(e)
_wrap.func_name = f.func_name _wrap.func_name = f.func_name
return _wrap return _wrap
@ -473,19 +520,19 @@ def create_engine(sql_connection):
engine_args["poolclass"] = StaticPool engine_args["poolclass"] = StaticPool
engine_args["connect_args"] = {'check_same_thread': False} engine_args["connect_args"] = {'check_same_thread': False}
elif all((CONF.sql_dbpool_enable, MySQLdb, elif all((CONF.sql_dbpool_enable, MySQLdb,
"mysql" in connection_dict.drivername)): "mysql" in connection_dict.drivername)):
LOG.info(_("Using mysql/eventlet db_pool.")) LOG.info(_("Using mysql/eventlet db_pool."))
# MySQLdb won't accept 'None' in the password field # MySQLdb won't accept 'None' in the password field
password = connection_dict.password or '' password = connection_dict.password or ''
pool_args = { pool_args = {
'db': connection_dict.database, 'db': connection_dict.database,
'passwd': password, 'passwd': password,
'host': connection_dict.host, 'host': connection_dict.host,
'user': connection_dict.username, 'user': connection_dict.username,
'min_size': CONF.sql_min_pool_size, 'min_size': CONF.sql_min_pool_size,
'max_size': CONF.sql_max_pool_size, 'max_size': CONF.sql_max_pool_size,
'max_idle': CONF.sql_idle_timeout, 'max_idle': CONF.sql_idle_timeout,
'client_flag': mysql_client_constants.FOUND_ROWS} 'client_flag': mysql_client_constants.FOUND_ROWS}
pool = db_pool.ConnectionPool(MySQLdb, **pool_args) pool = db_pool.ConnectionPool(MySQLdb, **pool_args)
@ -540,7 +587,7 @@ def create_engine(sql_connection):
break break
except OperationalError, e: except OperationalError, e:
if (remaining != 'infinite' and remaining == 0) or \ if (remaining != 'infinite' and remaining == 0) or \
not is_db_connection_error(e.args[0]): not is_db_connection_error(e.args[0]):
raise raise
return engine return engine
@ -599,15 +646,15 @@ def patch_mysqldb_with_stacktrace_comments():
continue continue
if file.endswith('exception.py') and method == '_wrap': if file.endswith('exception.py') and method == '_wrap':
continue continue
# nova/db/api is just a wrapper around nova/db/sqlalchemy/api # db/api is just a wrapper around db/sqlalchemy/api
if file.endswith('nova/db/api.py'): if file.endswith('db/api.py'):
continue continue
# only trace inside nova # only trace inside nova
index = file.rfind('nova') index = file.rfind('nova')
if index == -1: if index == -1:
continue continue
stack += "File:%s:%s Method:%s() Line:%s | " \ stack += "File:%s:%s Method:%s() Line:%s | " \
% (file[index:], line, method, function) % (file[index:], line, method, function)
# strip trailing " | " from stack # strip trailing " | " from stack
if stack: if stack:

View File

@ -22,13 +22,17 @@
import sqlalchemy import sqlalchemy
from nova import exception from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class InvalidSortKey(Exception):
message = _("Sort key supplied was not valid.")
# copy from glance/db/sqlalchemy/api.py # copy from glance/db/sqlalchemy/api.py
def paginate_query(query, model, limit, sort_keys, marker=None, def paginate_query(query, model, limit, sort_keys, marker=None,
sort_dir=None, sort_dirs=None): sort_dir=None, sort_dirs=None):
@ -89,7 +93,7 @@ def paginate_query(query, model, limit, sort_keys, marker=None,
try: try:
sort_key_attr = getattr(model, current_sort_key) sort_key_attr = getattr(model, current_sort_key)
except AttributeError: except AttributeError:
raise exception.InvalidSortKey() raise InvalidSortKey()
query = query.order_by(sort_dir_func(sort_key_attr)) query = query.order_by(sort_dir_func(sort_key_attr))
# Add pagination # Add pagination

View File

@ -37,9 +37,9 @@ import testtools
from nova import context from nova import context
from nova import db from nova import db
from nova.db import migration from nova.db import migration
from nova.db.sqlalchemy import session
from nova.network import manager as network_manager from nova.network import manager as network_manager
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.openstack.common import timeutils from nova.openstack.common import timeutils
from nova import paths from nova import paths
@ -56,8 +56,9 @@ test_opts = [
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(test_opts) CONF.register_opts(test_opts)
CONF.import_opt('sql_connection', 'nova.db.sqlalchemy.session') CONF.import_opt('sql_connection',
CONF.import_opt('sqlite_db', 'nova.db.sqlalchemy.session') 'nova.openstack.common.db.sqlalchemy.session')
CONF.import_opt('sqlite_db', 'nova.openstack.common.db.sqlalchemy.session')
CONF.set_override('use_stderr', False) CONF.set_override('use_stderr', False)
logging.setup('nova') logging.setup('nova')

View File

@ -18,6 +18,7 @@ Bare-metal DB testcase for BareMetalInterface
""" """
from nova import exception from nova import exception
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.tests.baremetal.db import base from nova.tests.baremetal.db import base
from nova.virt.baremetal import db from nova.virt.baremetal import db
@ -27,7 +28,7 @@ class BareMetalInterfaceTestCase(base.BMDBTestCase):
def test_unique_address(self): def test_unique_address(self):
pif1_id = db.bm_interface_create(self.context, 1, '11:11:11:11:11:11', pif1_id = db.bm_interface_create(self.context, 1, '11:11:11:11:11:11',
'0x1', 1) '0x1', 1)
self.assertRaises(exception.DBError, self.assertRaises(db_session.DBError,
db.bm_interface_create, db.bm_interface_create,
self.context, 2, '11:11:11:11:11:11', '0x2', 2) self.context, 2, '11:11:11:11:11:11', '0x2', 2)
# succeed after delete pif1 # succeed after delete pif1

View File

@ -18,6 +18,7 @@ Bare-metal DB testcase for BareMetalPxeIp
""" """
from nova import exception from nova import exception
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.tests.baremetal.db import base from nova.tests.baremetal.db import base
from nova.tests.baremetal.db import utils from nova.tests.baremetal.db import utils
from nova.virt.baremetal import db from nova.virt.baremetal import db
@ -50,14 +51,14 @@ class BareMetalPxeIpTestCase(base.BMDBTestCase):
# address duplicates # address duplicates
i = utils.new_bm_pxe_ip(address='10.1.1.1', i = utils.new_bm_pxe_ip(address='10.1.1.1',
server_address='10.1.1.201') server_address='10.1.1.201')
self.assertRaises(exception.DBError, self.assertRaises(db_session.DBError,
db.bm_pxe_ip_create_direct, db.bm_pxe_ip_create_direct,
self.context, i) self.context, i)
# server_address duplicates # server_address duplicates
i = utils.new_bm_pxe_ip(address='10.1.1.3', i = utils.new_bm_pxe_ip(address='10.1.1.3',
server_address='10.1.1.101') server_address='10.1.1.101')
self.assertRaises(exception.DBError, self.assertRaises(db_session.DBError,
db.bm_pxe_ip_create_direct, db.bm_pxe_ip_create_direct,
self.context, i) self.context, i)

View File

@ -25,6 +25,7 @@ from testtools import matchers
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.tests.baremetal.db import base as bm_db_base from nova.tests.baremetal.db import base as bm_db_base
from nova.tests.baremetal.db import utils as bm_db_utils from nova.tests.baremetal.db import utils as bm_db_utils
from nova.tests.image import fake as fake_image from nova.tests.image import fake as fake_image
@ -521,7 +522,7 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase):
AndRaise(exception.NovaException) AndRaise(exception.NovaException)
bm_utils.unlink_without_raise(pxe_path) bm_utils.unlink_without_raise(pxe_path)
self.driver._collect_mac_addresses(self.context, self.node).\ self.driver._collect_mac_addresses(self.context, self.node).\
AndRaise(exception.DBError) AndRaise(db_session.DBError)
bm_utils.rmtree_without_raise( bm_utils.rmtree_without_raise(
os.path.join(CONF.baremetal.tftp_root, 'fake-uuid')) os.path.join(CONF.baremetal.tftp_root, 'fake-uuid'))
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -29,6 +29,7 @@ from nova.network import linux_net
from nova.network import manager as network_manager from nova.network import manager as network_manager
from nova.network import model as net_model from nova.network import model as net_model
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import importutils from nova.openstack.common import importutils
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.openstack.common import rpc from nova.openstack.common import rpc
@ -2046,7 +2047,7 @@ class FloatingIPTestCase(test.TestCase):
# address column, so fake the collision-avoidance here # address column, so fake the collision-avoidance here
def fake_vif_save(vif): def fake_vif_save(vif):
if vif.address == crash_test_dummy_vif['address']: if vif.address == crash_test_dummy_vif['address']:
raise exception.DBError("If you're smart, you'll retry!") raise db_session.DBError("If you're smart, you'll retry!")
self.stubs.Set(models.VirtualInterface, 'save', fake_vif_save) self.stubs.Set(models.VirtualInterface, 'save', fake_vif_save)
# Attempt to add another and make sure that both MACs are consumed # Attempt to add another and make sure that both MACs are consumed

View File

@ -21,8 +21,8 @@ from nova.compute import instance_types
from nova import context from nova import context
from nova import db from nova import db
from nova.db.sqlalchemy import models from nova.db.sqlalchemy import models
from nova.db.sqlalchemy import session as sql_session
from nova import exception from nova import exception
from nova.openstack.common.db.sqlalchemy import session as sql_session
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova import test from nova import test

View File

@ -1,129 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 Rackspace Hosting
# 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.
"""Unit tests for SQLAlchemy specific code."""
from eventlet import db_pool
try:
import MySQLdb
except ImportError:
MySQLdb = None
from sqlalchemy import Column, MetaData, Table, UniqueConstraint
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import DateTime, Integer
from nova import context
from nova.db.sqlalchemy import models
from nova.db.sqlalchemy import session
from nova import exception
from nova import test
class DbPoolTestCase(test.TestCase):
def setUp(self):
super(DbPoolTestCase, self).setUp()
self.flags(sql_dbpool_enable=True)
self.user_id = 'fake'
self.project_id = 'fake'
self.context = context.RequestContext(self.user_id, self.project_id)
if not MySQLdb:
self.skipTest("Unable to test due to lack of MySQLdb")
def test_db_pool_option(self):
self.flags(sql_idle_timeout=11, sql_min_pool_size=21,
sql_max_pool_size=42)
info = {}
class FakeConnectionPool(db_pool.ConnectionPool):
def __init__(self, mod_name, **kwargs):
info['module'] = mod_name
info['kwargs'] = kwargs
super(FakeConnectionPool, self).__init__(mod_name,
**kwargs)
def connect(self, *args, **kwargs):
raise test.TestingException()
self.stubs.Set(db_pool, 'ConnectionPool',
FakeConnectionPool)
sql_connection = 'mysql://user:pass@127.0.0.1/nova'
self.assertRaises(test.TestingException, session.create_engine,
sql_connection)
self.assertEqual(info['module'], MySQLdb)
self.assertEqual(info['kwargs']['max_idle'], 11)
self.assertEqual(info['kwargs']['min_size'], 21)
self.assertEqual(info['kwargs']['max_size'], 42)
BASE = declarative_base()
_TABLE_NAME = '__tmp__test__tmp__'
class TmpTable(BASE, models.NovaBase):
__tablename__ = _TABLE_NAME
id = Column(Integer, primary_key=True)
foo = Column(Integer)
class SessionErrorWrapperTestCase(test.TestCase):
def setUp(self):
super(SessionErrorWrapperTestCase, self).setUp()
meta = MetaData()
meta.bind = session.get_engine()
test_table = Table(_TABLE_NAME, meta,
Column('id', Integer, primary_key=True,
nullable=False),
Column('deleted', Integer, default=0),
Column('deleted_at', DateTime),
Column('updated_at', DateTime),
Column('created_at', DateTime),
Column('foo', Integer),
UniqueConstraint('foo', name='uniq_foo'))
test_table.create()
def tearDown(self):
super(SessionErrorWrapperTestCase, self).tearDown()
meta = MetaData()
meta.bind = session.get_engine()
test_table = Table(_TABLE_NAME, meta, autoload=True)
test_table.drop()
def test_flush_wrapper(self):
tbl = TmpTable()
tbl.update({'foo': 10})
tbl.save()
tbl2 = TmpTable()
tbl2.update({'foo': 10})
self.assertRaises(exception.DBDuplicateEntry, tbl2.save)
def test_execute_wrapper(self):
_session = session.get_session()
with _session.begin():
for i in [10, 20]:
tbl = TmpTable()
tbl.update({'foo': i})
tbl.save(session=_session)
method = _session.query(TmpTable).\
filter_by(foo=10).\
update
self.assertRaises(exception.DBDuplicateEntry,
method, {'foo': 20})

View File

@ -351,7 +351,7 @@ def bm_interface_set_vif_uuid(context, if_id, vif_uuid):
try: try:
session.add(bm_interface) session.add(bm_interface)
session.flush() session.flush()
except exception.DBError, e: except db_session.DBError, e:
# TODO(deva): clean up when db layer raises DuplicateKeyError # TODO(deva): clean up when db layer raises DuplicateKeyError
if str(e).find('IntegrityError') != -1: if str(e).find('IntegrityError') != -1:
raise exception.NovaException(_("Baremetal interface %s " raise exception.NovaException(_("Baremetal interface %s "

View File

@ -19,8 +19,8 @@
"""Session Handling for SQLAlchemy backend.""" """Session Handling for SQLAlchemy backend."""
from nova.db.sqlalchemy import session as nova_session
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as nova_session
from nova import paths from nova import paths
opts = [ opts = [
@ -38,11 +38,13 @@ CONF = cfg.CONF
CONF.register_group(baremetal_group) CONF.register_group(baremetal_group)
CONF.register_opts(opts, baremetal_group) CONF.register_opts(opts, baremetal_group)
CONF.import_opt('sqlite_db', 'nova.db.sqlalchemy.session') CONF.import_opt('sqlite_db', 'nova.openstack.common.db.sqlalchemy.session')
_ENGINE = None _ENGINE = None
_MAKER = None _MAKER = None
DBError = nova_session.DBError
def get_session(autocommit=True, expire_on_commit=False): def get_session(autocommit=True, expire_on_commit=False):
"""Return a SQLAlchemy session.""" """Return a SQLAlchemy session."""

View File

@ -25,6 +25,7 @@ from nova.compute import power_state
from nova import context as nova_context from nova import context as nova_context
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import importutils from nova.openstack.common import importutils
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova import paths from nova import paths
@ -266,7 +267,7 @@ class BareMetalDriver(driver.ComputeDriver):
pm.state = baremetal_states.ERROR pm.state = baremetal_states.ERROR
try: try:
_update_state(context, node, instance, pm.state) _update_state(context, node, instance, pm.state)
except exception.DBError, e: except db_session.DBError, e:
LOG.warning(_("Failed to update state record for " LOG.warning(_("Failed to update state record for "
"baremetal node %s") % instance['uuid']) "baremetal node %s") % instance['uuid'])

View File

@ -25,6 +25,7 @@ import os
from nova.compute import instance_types from nova.compute import instance_types
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import fileutils from nova.openstack.common import fileutils
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.virt.baremetal import base from nova.virt.baremetal import base
@ -411,7 +412,7 @@ class PXE(base.NodeDriver):
bm_utils.unlink_without_raise(get_pxe_config_file_path(instance)) bm_utils.unlink_without_raise(get_pxe_config_file_path(instance))
try: try:
macs = self._collect_mac_addresses(context, node) macs = self._collect_mac_addresses(context, node)
except exception.DBError: except db_session.DBError:
pass pass
else: else:
for mac in macs: for mac in macs:

View File

@ -1,7 +1,7 @@
[DEFAULT] [DEFAULT]
# The list of modules to copy from openstack-common # The list of modules to copy from openstack-common
modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils,install_venv_common,flakes modules=cfg,cliutils,context,db,db.sqlalchemy,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils,install_venv_common,flakes
# The base module to hold the copy of openstack.common # The base module to hold the copy of openstack.common
base=nova base=nova