From cd0c765ced2823c05b16126d6ba6a823bb66fe7d Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Thu, 17 Jan 2013 15:38:02 -0500 Subject: [PATCH] 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 --- bin/nova-manage | 9 +- nova/compute/instance_types.py | 3 +- nova/config.py | 6 + nova/db/sqlalchemy/api.py | 15 +- nova/db/sqlalchemy/migration.py | 4 +- nova/db/sqlalchemy/models.py | 72 +--------- nova/exception.py | 14 -- nova/openstack/common/db/__init__.py | 16 +++ .../common/db/sqlalchemy/__init__.py | 16 +++ nova/openstack/common/db/sqlalchemy/models.py | 103 ++++++++++++++ .../common}/db/sqlalchemy/session.py | 93 +++++++++---- .../common/db/sqlalchemy/utils.py} | 8 +- nova/test.py | 7 +- nova/tests/baremetal/db/test_bm_interface.py | 3 +- nova/tests/baremetal/db/test_bm_pxe_ip.py | 5 +- nova/tests/baremetal/test_pxe.py | 3 +- nova/tests/network/test_manager.py | 3 +- nova/tests/test_instance_types.py | 2 +- nova/tests/test_sqlalchemy.py | 129 ------------------ nova/virt/baremetal/db/sqlalchemy/api.py | 2 +- nova/virt/baremetal/db/sqlalchemy/session.py | 6 +- nova/virt/baremetal/driver.py | 3 +- nova/virt/baremetal/pxe.py | 3 +- openstack-common.conf | 2 +- 24 files changed, 263 insertions(+), 264 deletions(-) create mode 100644 nova/openstack/common/db/__init__.py create mode 100644 nova/openstack/common/db/sqlalchemy/__init__.py create mode 100644 nova/openstack/common/db/sqlalchemy/models.py rename nova/{ => openstack/common}/db/sqlalchemy/session.py (90%) rename nova/{common/sqlalchemyutils.py => openstack/common/db/sqlalchemy/utils.py} (96%) delete mode 100644 nova/tests/test_sqlalchemy.py diff --git a/bin/nova-manage b/bin/nova-manage index 82edf738939c..c793fed16f24 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -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='', @@ -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) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 78129ee6b1aa..30766fd9e46b 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -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() diff --git a/nova/config.py b/nova/config.py index 4095dba75d36..18147bdbb93f 100644 --- a/nova/config.py +++ b/nova/config.py @@ -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', diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0154805ac55b..6a973e59f817 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -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 diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py index dbc1ed432617..421167bec306 100644 --- a/nova/db/sqlalchemy/migration.py +++ b/nova/db/sqlalchemy/migration.py @@ -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: diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index b4c680ac02b8..78629e6c9dc2 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -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): diff --git a/nova/exception.py b/nova/exception.py index 6bb8097c330f..3b20b7e78276 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -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") diff --git a/nova/openstack/common/db/__init__.py b/nova/openstack/common/db/__init__.py new file mode 100644 index 000000000000..1b9b60dec103 --- /dev/null +++ b/nova/openstack/common/db/__init__.py @@ -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. diff --git a/nova/openstack/common/db/sqlalchemy/__init__.py b/nova/openstack/common/db/sqlalchemy/__init__.py new file mode 100644 index 000000000000..1b9b60dec103 --- /dev/null +++ b/nova/openstack/common/db/sqlalchemy/__init__.py @@ -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. diff --git a/nova/openstack/common/db/sqlalchemy/models.py b/nova/openstack/common/db/sqlalchemy/models.py new file mode 100644 index 000000000000..87ec7ccc341c --- /dev/null +++ b/nova/openstack/common/db/sqlalchemy/models.py @@ -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) diff --git a/nova/db/sqlalchemy/session.py b/nova/openstack/common/db/sqlalchemy/session.py similarity index 90% rename from nova/db/sqlalchemy/session.py rename to nova/openstack/common/db/sqlalchemy/session.py index 28ec613c5176..2125b006de24 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/openstack/common/db/sqlalchemy/session.py @@ -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: diff --git a/nova/common/sqlalchemyutils.py b/nova/openstack/common/db/sqlalchemy/utils.py similarity index 96% rename from nova/common/sqlalchemyutils.py rename to nova/openstack/common/db/sqlalchemy/utils.py index a186948ac96c..ef8af57ce6de 100644 --- a/nova/common/sqlalchemyutils.py +++ b/nova/openstack/common/db/sqlalchemy/utils.py @@ -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 diff --git a/nova/test.py b/nova/test.py index b3f851dc4644..e5c11081c2a9 100644 --- a/nova/test.py +++ b/nova/test.py @@ -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') diff --git a/nova/tests/baremetal/db/test_bm_interface.py b/nova/tests/baremetal/db/test_bm_interface.py index 9f051ac9bc72..32beb1ce0a60 100644 --- a/nova/tests/baremetal/db/test_bm_interface.py +++ b/nova/tests/baremetal/db/test_bm_interface.py @@ -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 diff --git a/nova/tests/baremetal/db/test_bm_pxe_ip.py b/nova/tests/baremetal/db/test_bm_pxe_ip.py index 9a93b46ada93..9820f3af0711 100644 --- a/nova/tests/baremetal/db/test_bm_pxe_ip.py +++ b/nova/tests/baremetal/db/test_bm_pxe_ip.py @@ -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) diff --git a/nova/tests/baremetal/test_pxe.py b/nova/tests/baremetal/test_pxe.py index 09f1079bf40e..e50462b0eb93 100644 --- a/nova/tests/baremetal/test_pxe.py +++ b/nova/tests/baremetal/test_pxe.py @@ -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() diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index 48183010f781..9d467bcb1f3e 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -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 diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index b70b96b7fee9..5abf62c3e7b6 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -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 diff --git a/nova/tests/test_sqlalchemy.py b/nova/tests/test_sqlalchemy.py deleted file mode 100644 index 5c7f4450b54a..000000000000 --- a/nova/tests/test_sqlalchemy.py +++ /dev/null @@ -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}) diff --git a/nova/virt/baremetal/db/sqlalchemy/api.py b/nova/virt/baremetal/db/sqlalchemy/api.py index 34bcd122901e..198c06256273 100644 --- a/nova/virt/baremetal/db/sqlalchemy/api.py +++ b/nova/virt/baremetal/db/sqlalchemy/api.py @@ -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 " diff --git a/nova/virt/baremetal/db/sqlalchemy/session.py b/nova/virt/baremetal/db/sqlalchemy/session.py index fcaf210a5665..06d7773543b0 100644 --- a/nova/virt/baremetal/db/sqlalchemy/session.py +++ b/nova/virt/baremetal/db/sqlalchemy/session.py @@ -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.""" diff --git a/nova/virt/baremetal/driver.py b/nova/virt/baremetal/driver.py index 631a9a8c4f79..43af951fda4f 100644 --- a/nova/virt/baremetal/driver.py +++ b/nova/virt/baremetal/driver.py @@ -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']) diff --git a/nova/virt/baremetal/pxe.py b/nova/virt/baremetal/pxe.py index 5a6f5865528d..a169e13e5db3 100644 --- a/nova/virt/baremetal/pxe.py +++ b/nova/virt/baremetal/pxe.py @@ -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: diff --git a/openstack-common.conf b/openstack-common.conf index b0db41d512b8..29ed9d82f969 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -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