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

View File

@ -27,6 +27,7 @@ from nova import context
from nova import db
from nova import exception
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 import utils
@ -110,7 +111,7 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=None, flavorid=None,
try:
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)
raise exception.InstanceTypeCreateFailed()

View File

@ -18,10 +18,16 @@
# under the License.
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 import paths
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('$sqlite_db')
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')
cfg.CONF(argv[1:],
project='nova',

View File

@ -35,14 +35,14 @@ from sqlalchemy.sql.expression import desc
from sqlalchemy.sql import func
from nova import block_device
from nova.common import sqlalchemyutils
from nova.compute import task_states
from nova.compute import vm_states
from nova import db
from nova.db.sqlalchemy import models
from nova.db.sqlalchemy.session import get_session
from nova import exception
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 timeutils
from nova.openstack.common import uuidutils
@ -58,10 +58,13 @@ db_opts = [
CONF = cfg.CONF
CONF.register_opts(db_opts)
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__)
get_session = db_session.get_session
def is_user_context(context):
"""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.update(values)
vif_ref.save()
except exception.DBError:
except db_session.DBError:
raise exception.VirtualInterfaceCreateException()
return vif_ref
@ -3535,7 +3538,7 @@ def instance_type_create(context, values):
instance_type_ref.update(values)
instance_type_ref.save(session=session)
except Exception, e:
raise exception.DBError(e)
raise db_session.DBError(e)
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.save()
except Exception, e:
raise exception.DBError(e)
raise db_session.DBError(e)
return s3_image_ref

View File

@ -20,8 +20,8 @@ import distutils.version as dist_version
import os
from nova.db import migration
from nova.db.sqlalchemy.session import get_engine
from nova import exception
from nova.openstack.common.db.sqlalchemy import session as db_session
from nova.openstack.common import log as logging
@ -62,6 +62,8 @@ from migrate.versioning.repository import Repository
_REPOSITORY = None
get_engine = db_session.get_engine
def db_sync(version=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.orm import relationship, backref, object_mapper
from nova.db.sqlalchemy.session import get_session
from nova.db.sqlalchemy import types
from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import models
from nova.openstack.common import timeutils
@ -36,74 +36,8 @@ CONF = cfg.CONF
BASE = declarative_base()
class NovaBase(object):
"""Base class for Nova Models."""
__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 NovaBase(models.SoftDeleteMixin, models.ModelBase):
pass
class Service(BASE, NovaBase):

View File

@ -164,20 +164,6 @@ class EC2APIError(NovaException):
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):
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.
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:
* 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.
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:
* 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.
"""
import os.path
import re
import time
@ -238,16 +258,17 @@ import sqlalchemy.orm
from sqlalchemy.pool import NullPool, StaticPool
from sqlalchemy.sql.expression import literal_column
import nova.exception
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 import paths
sql_opts = [
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 '
'database'),
cfg.StrOpt('sqlite_db',
@ -262,11 +283,11 @@ sql_opts = [
cfg.IntOpt('sql_min_pool_size',
default=1,
help='Minimum number of SQL connections to keep open in a '
'pool'),
'pool'),
cfg.IntOpt('sql_max_pool_size',
default=5,
help='Maximum number of SQL connections to keep open in a '
'pool'),
'pool'),
cfg.IntOpt('sql_max_retries',
default=10,
help='maximum db connection retries during startup. '
@ -297,6 +318,13 @@ _ENGINE = 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):
"""Return a SQLAlchemy session."""
global _MAKER
@ -309,6 +337,25 @@ def get_session(autocommit=True, expire_on_commit=False):
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
# violation messages follow the structure:
#
@ -362,7 +409,7 @@ def raise_if_duplicate_entry_error(integrity_error, engine_name):
columns = columns.strip().split(", ")
else:
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):
@ -370,7 +417,7 @@ def wrap_db_error(f):
try:
return f(*args, **kwargs)
except UnicodeEncodeError:
raise nova.exception.InvalidUnicodeParameter()
raise InvalidUnicodeParameter()
# note(boris-42): We should catch unique constraint violation and
# wrap it by our own DBDuplicateEntry exception. Unique constraint
# violation is wrapped by IntegrityError.
@ -381,10 +428,10 @@ def wrap_db_error(f):
# means we should get names of columns, which values violate
# unique constraint, from error message.
raise_if_duplicate_entry_error(e, get_engine().name)
raise nova.exception.DBError(e)
raise DBError(e)
except Exception, e:
LOG.exception(_('DB exception wrapped.'))
raise nova.exception.DBError(e)
raise DBError(e)
_wrap.func_name = f.func_name
return _wrap
@ -473,19 +520,19 @@ def create_engine(sql_connection):
engine_args["poolclass"] = StaticPool
engine_args["connect_args"] = {'check_same_thread': False}
elif all((CONF.sql_dbpool_enable, MySQLdb,
"mysql" in connection_dict.drivername)):
"mysql" in connection_dict.drivername)):
LOG.info(_("Using mysql/eventlet db_pool."))
# MySQLdb won't accept 'None' in the password field
password = connection_dict.password or ''
pool_args = {
'db': connection_dict.database,
'passwd': password,
'host': connection_dict.host,
'user': connection_dict.username,
'min_size': CONF.sql_min_pool_size,
'max_size': CONF.sql_max_pool_size,
'max_idle': CONF.sql_idle_timeout,
'client_flag': mysql_client_constants.FOUND_ROWS}
'db': connection_dict.database,
'passwd': password,
'host': connection_dict.host,
'user': connection_dict.username,
'min_size': CONF.sql_min_pool_size,
'max_size': CONF.sql_max_pool_size,
'max_idle': CONF.sql_idle_timeout,
'client_flag': mysql_client_constants.FOUND_ROWS}
pool = db_pool.ConnectionPool(MySQLdb, **pool_args)
@ -540,7 +587,7 @@ def create_engine(sql_connection):
break
except OperationalError, e:
if (remaining != 'infinite' and remaining == 0) or \
not is_db_connection_error(e.args[0]):
not is_db_connection_error(e.args[0]):
raise
return engine
@ -599,15 +646,15 @@ def patch_mysqldb_with_stacktrace_comments():
continue
if file.endswith('exception.py') and method == '_wrap':
continue
# nova/db/api is just a wrapper around nova/db/sqlalchemy/api
if file.endswith('nova/db/api.py'):
# db/api is just a wrapper around db/sqlalchemy/api
if file.endswith('db/api.py'):
continue
# only trace inside nova
index = file.rfind('nova')
if index == -1:
continue
stack += "File:%s:%s Method:%s() Line:%s | " \
% (file[index:], line, method, function)
% (file[index:], line, method, function)
# strip trailing " | " from stack
if stack:

View File

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

View File

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

View File

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

View File

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

View File

@ -25,6 +25,7 @@ from testtools import matchers
from nova import exception
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 utils as bm_db_utils
from nova.tests.image import fake as fake_image
@ -521,7 +522,7 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase):
AndRaise(exception.NovaException)
bm_utils.unlink_without_raise(pxe_path)
self.driver._collect_mac_addresses(self.context, self.node).\
AndRaise(exception.DBError)
AndRaise(db_session.DBError)
bm_utils.rmtree_without_raise(
os.path.join(CONF.baremetal.tftp_root, 'fake-uuid'))
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 model as net_model
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 log as logging
from nova.openstack.common import rpc
@ -2046,7 +2047,7 @@ class FloatingIPTestCase(test.TestCase):
# address column, so fake the collision-avoidance here
def fake_vif_save(vif):
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)
# 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 db
from nova.db.sqlalchemy import models
from nova.db.sqlalchemy import session as sql_session
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 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:
session.add(bm_interface)
session.flush()
except exception.DBError, e:
except db_session.DBError, e:
# TODO(deva): clean up when db layer raises DuplicateKeyError
if str(e).find('IntegrityError') != -1:
raise exception.NovaException(_("Baremetal interface %s "

View File

@ -19,8 +19,8 @@
"""Session Handling for SQLAlchemy backend."""
from nova.db.sqlalchemy import session as nova_session
from nova.openstack.common import cfg
from nova.openstack.common.db.sqlalchemy import session as nova_session
from nova import paths
opts = [
@ -38,11 +38,13 @@ CONF = cfg.CONF
CONF.register_group(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
_MAKER = None
DBError = nova_session.DBError
def get_session(autocommit=True, expire_on_commit=False):
"""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 exception
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 log as logging
from nova import paths
@ -266,7 +267,7 @@ class BareMetalDriver(driver.ComputeDriver):
pm.state = baremetal_states.ERROR
try:
_update_state(context, node, instance, pm.state)
except exception.DBError, e:
except db_session.DBError, e:
LOG.warning(_("Failed to update state record for "
"baremetal node %s") % instance['uuid'])

View File

@ -25,6 +25,7 @@ import os
from nova.compute import instance_types
from nova import exception
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 log as logging
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))
try:
macs = self._collect_mac_addresses(context, node)
except exception.DBError:
except db_session.DBError:
pass
else:
for mac in macs:

View File

@ -1,7 +1,7 @@
[DEFAULT]
# 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
base=nova