diff --git a/keystone/tests/core.py b/keystone/tests/core.py index 1ca4a4e4c3..bbc9bde692 100644 --- a/keystone/tests/core.py +++ b/keystone/tests/core.py @@ -45,12 +45,10 @@ from keystone import auth from keystone.common import dependency from keystone.common import kvs from keystone.common.kvs import core as kvs_core -from keystone.common import sql from keystone.common import utils as common_utils from keystone import config from keystone import exception from keystone import notifications -from keystone.openstack.common.db import options as db_options from keystone.openstack.common.fixture import config as config_fixture from keystone.openstack.common.gettextutils import _ from keystone.openstack.common import log @@ -119,19 +117,6 @@ class dirs: DEFAULT_TEST_DB_FILE = dirs.tmp('test.db') -def _initialize_sql_session(): - # Make sure the DB is located in the correct location, in this case set - # the default value, as this should be able to be overridden in some - # test cases. - db_file = DEFAULT_TEST_DB_FILE - db_options.set_defaults( - sql_connection=IN_MEM_DB_CONN_STRING, - sqlite_db=db_file) - - -_initialize_sql_session() - - def checkout_vendor(repo, rev): # TODO(termie): this function is a good target for some optimizations :PERF name = repo.split('/')[-1] @@ -459,15 +444,6 @@ class TestCase(BaseTestCase): setattr(self, manager_name, manager) self.addCleanup(self.cleanup_instance(*drivers.keys())) - # The credential backend only supports SQL, so we always have to load - # the tables. - self.engine = sql.get_engine() - self.addCleanup(sql.cleanup) - self.addCleanup(self.cleanup_instance('engine')) - - sql.ModelBase.metadata.create_all(bind=self.engine) - self.addCleanup(sql.ModelBase.metadata.drop_all, bind=self.engine) - def load_fixtures(self, fixtures): """Hacky basic and naive fixture loading based on a python module. diff --git a/keystone/tests/ksfixtures/database.py b/keystone/tests/ksfixtures/database.py new file mode 100644 index 0000000000..23341fa7cc --- /dev/null +++ b/keystone/tests/ksfixtures/database.py @@ -0,0 +1,105 @@ +# 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. + +import functools +import os +import shutil + +import fixtures + +from keystone.common import sql +from keystone.common.sql import migration_helpers +from keystone import config +from keystone.openstack.common.db import options as db_options +from keystone.openstack.common.db.sqlalchemy import migration +from keystone import tests + + +CONF = config.CONF + + +def run_once(f): + @functools.wraps(f) + def wrapper(): + if not wrapper.already_ran: + f() + wrapper.already_ran = True + wrapper.already_ran = False + return wrapper + + +def _setup_database(extensions=None): + if CONF.database.connection != tests.IN_MEM_DB_CONN_STRING: + db = tests.dirs.tmp('test.db') + pristine = tests.dirs.tmp('test.db.pristine') + + if os.path.exists(db): + os.unlink(db) + if not os.path.exists(pristine): + migration.db_sync(sql.get_engine(), + migration_helpers.find_migrate_repo()) + for extension in (extensions or []): + migration_helpers.sync_database_to_version(extension=extension) + shutil.copyfile(db, pristine) + else: + shutil.copyfile(pristine, db) + + +@run_once +def _initialize_sql_session(): + # Make sure the DB is located in the correct location, in this case set + # the default value, as this should be able to be overridden in some + # test cases. + db_options.set_defaults( + sql_connection=tests.IN_MEM_DB_CONN_STRING, + sqlite_db=tests.DEFAULT_TEST_DB_FILE) + + +@run_once +def _load_sqlalchemy_models(): + """Find all modules containing SQLAlchemy models and import them. + + This will create more consistent, deterministic test runs because the + database schema will be predictable. The schema is created based on the + models already imported. This can change during the course of a test run. + If all models are imported ahead of time then the schema will always be + the same. + + """ + keystone_root = os.path.normpath(os.path.join( + os.path.dirname(__file__), '..', '..')) + for root, dirs, files in os.walk(keystone_root): + if root.endswith('backends') and 'sql.py' in files: + module_name = root.replace(os.sep, '.') + '.sql' + __import__(module_name) + + +class Database(fixtures.Fixture): + """A fixture for setting up and tearing down a database. + + """ + + def __init__(self, extensions=None): + super(Database, self).__init__() + self._extensions = extensions + _initialize_sql_session() + _load_sqlalchemy_models() + + def setUp(self): + super(Database, self).setUp() + _setup_database(extensions=self._extensions) + + self.engine = sql.get_engine() + sql.ModelBase.metadata.create_all(bind=self.engine) + self.addCleanup(sql.cleanup) + self.addCleanup(sql.ModelBase.metadata.drop_all, bind=self.engine) diff --git a/keystone/tests/test_auth.py b/keystone/tests/test_auth.py index 2941c3507e..90fa73b5a8 100644 --- a/keystone/tests/test_auth.py +++ b/keystone/tests/test_auth.py @@ -27,6 +27,7 @@ from keystone import exception from keystone.openstack.common import timeutils from keystone import tests from keystone.tests import default_fixtures +from keystone.tests.ksfixtures import database from keystone import token from keystone import trust @@ -68,6 +69,7 @@ def _build_user_auth(token=None, user_id=None, username=None, class AuthTest(tests.TestCase): def setUp(self): + self.useFixture(database.Database()) super(AuthTest, self).setUp() self.load_backends() diff --git a/keystone/tests/test_backend_kvs.py b/keystone/tests/test_backend_kvs.py index 2fdf9f57b8..1bbde9efd2 100644 --- a/keystone/tests/test_backend_kvs.py +++ b/keystone/tests/test_backend_kvs.py @@ -20,11 +20,16 @@ from keystone import exception from keystone.openstack.common import timeutils from keystone import tests from keystone.tests import default_fixtures +from keystone.tests.ksfixtures import database from keystone.tests import test_backend class KvsIdentity(tests.TestCase, test_backend.IdentityTests): def setUp(self): + # NOTE(dstanek): setup the database for subsystems that do not have a + # KVS backend (like credentials) + self.useFixture(database.Database()) + super(KvsIdentity, self).setUp() self.load_backends() self.load_fixtures(default_fixtures) diff --git a/keystone/tests/test_backend_ldap.py b/keystone/tests/test_backend_ldap.py index 1be03a364e..4e17c86939 100644 --- a/keystone/tests/test_backend_ldap.py +++ b/keystone/tests/test_backend_ldap.py @@ -32,6 +32,7 @@ from keystone import identity from keystone import tests from keystone.tests import default_fixtures from keystone.tests import fakeldap +from keystone.tests.ksfixtures import database from keystone.tests import test_backend @@ -592,6 +593,14 @@ class BaseLDAPIdentity(test_backend.IdentityTests): class LDAPIdentity(BaseLDAPIdentity, tests.TestCase): + + def setUp(self): + # NOTE(dstanek): The database must be setup prior to calling the + # parent's setUp. The parent's setUp uses services (like + # credentials) that require a database. + self.useFixture(database.Database()) + super(LDAPIdentity, self).setUp() + def test_configurable_allowed_project_actions(self): tenant = {'id': u'fäké1', 'name': u'fäké1', 'enabled': True} self.assignment_api.create_project(u'fäké1', tenant) @@ -1332,6 +1341,7 @@ class LdapIdentitySqlAssignment(BaseLDAPIdentity, tests.SQLDriverOverrides, return config_files def setUp(self): + self.useFixture(database.Database()) super(LdapIdentitySqlAssignment, self).setUp() self.clear_database() self.load_backends() @@ -1416,6 +1426,7 @@ class MultiLDAPandSQLIdentity(BaseLDAPIdentity, tests.SQLDriverOverrides, """ def setUp(self): + self.useFixture(database.Database()) super(MultiLDAPandSQLIdentity, self).setUp() self.load_backends() diff --git a/keystone/tests/test_backend_sql.py b/keystone/tests/test_backend_sql.py index beb217313f..6c0aa35105 100644 --- a/keystone/tests/test_backend_sql.py +++ b/keystone/tests/test_backend_sql.py @@ -26,6 +26,7 @@ from keystone.identity.backends import sql as identity_sql from keystone.openstack.common.db import exception as db_exception from keystone import tests from keystone.tests import default_fixtures +from keystone.tests.ksfixtures import database from keystone.tests import test_backend from keystone.token.backends import sql as token_sql @@ -38,6 +39,7 @@ class SqlTests(tests.SQLDriverOverrides, tests.TestCase): def setUp(self): super(SqlTests, self).setUp() + self.useFixture(database.Database()) self.load_backends() # populate the engine with tables & fixtures @@ -52,15 +54,10 @@ class SqlTests(tests.SQLDriverOverrides, tests.TestCase): class SqlModels(SqlTests): - def setUp(self): - super(SqlModels, self).setUp() - - self.metadata = sql.ModelBase.metadata - self.metadata.bind = self.engine def select_table(self, name): table = sqlalchemy.Table(name, - self.metadata, + sql.ModelBase.metadata, autoload=True) s = sqlalchemy.select([table]) return s diff --git a/keystone/tests/test_keystoneclient.py b/keystone/tests/test_keystoneclient.py index e39b166d41..c862ad85ca 100644 --- a/keystone/tests/test_keystoneclient.py +++ b/keystone/tests/test_keystoneclient.py @@ -25,6 +25,7 @@ from keystone.openstack.common import timeutils from keystone import tests from keystone.tests import default_fixtures from keystone.tests.ksfixtures import appserver +from keystone.tests.ksfixtures import database CONF = config.CONF @@ -43,6 +44,7 @@ class CompatTestCase(tests.NoModule, tests.TestCase): # approach is to ensure we have the correct backing store. The # credential api makes some very SQL specific assumptions that should # be addressed allowing for non-SQL based testing to occur. + self.useFixture(database.Database()) self.load_backends() self.load_fixtures(default_fixtures) diff --git a/keystone/tests/test_v3.py b/keystone/tests/test_v3.py index 50f5a48271..80ed57679f 100644 --- a/keystone/tests/test_v3.py +++ b/keystone/tests/test_v3.py @@ -13,8 +13,6 @@ # under the License. import datetime -import os -import shutil import uuid from lxml import etree @@ -25,15 +23,13 @@ from keystone import auth from keystone.common import authorization from keystone.common import cache from keystone.common import serializer -from keystone.common import sql -from keystone.common.sql import migration_helpers from keystone import config from keystone import exception from keystone import middleware -from keystone.openstack.common.db.sqlalchemy import migration from keystone.openstack.common import timeutils from keystone.policy.backends import rules from keystone import tests +from keystone.tests.ksfixtures import database from keystone.tests import rest @@ -43,27 +39,6 @@ DEFAULT_DOMAIN_ID = 'default' TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' -def _setup_database(extensions=None): - if CONF.database.connection != tests.IN_MEM_DB_CONN_STRING: - db = tests.dirs.tmp('test.db') - pristine = tests.dirs.tmp('test.db.pristine') - - if os.path.exists(db): - os.unlink(db) - if not os.path.exists(pristine): - migration.db_sync(sql.get_engine(), - migration_helpers.find_migrate_repo()) - for extension in (extensions or []): - migration_helpers.sync_database_to_version(extension=extension) - shutil.copyfile(db, pristine) - else: - shutil.copyfile(pristine, db) - - -def _teardown_database(): - sql.cleanup() - - class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): def config_files(self): config_files = super(RestfulTestCase, self).config_files() @@ -76,12 +51,6 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): extensions.add(self.EXTENSION_NAME) return extensions - def setup_database(self): - _setup_database(self.get_extensions()) - - def teardown_database(self): - _teardown_database() - def generate_paste_config(self): new_paste_file = None try: @@ -108,6 +77,8 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): if new_paste_file: app_conf = 'config:%s' % (new_paste_file) + self.useFixture(database.Database(self.get_extensions())) + super(RestfulTestCase, self).setUp(app_conf=app_conf) self.empty_context = {'environment': {}} @@ -115,11 +86,7 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): # drop the policy rules self.addCleanup(rules.reset) - self.addCleanup(self.teardown_database) - def load_backends(self): - self.setup_database() - # ensure the cache region instance is setup cache.configure_cache_region(cache.REGION)