Add tests for db migrations
Change-Id: I4b445c6b0b06d3c762426e16c52513c2510dda73
This commit is contained in:
		| @@ -153,3 +153,7 @@ class SIGHUPInterrupt(GlareException): | |||||||
|  |  | ||||||
| class WorkerCreationFailure(GlareException): | class WorkerCreationFailure(GlareException): | ||||||
|     message = _("Server worker creation failed: %(reason)s.") |     message = _("Server worker creation failed: %(reason)s.") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DBNotAllowed(GlareException): | ||||||
|  |     msg_fmt = _('This operation is not allowed with current DB') | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								glare/tests/unit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								glare/tests/unit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								glare/tests/unit/db/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								glare/tests/unit/db/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								glare/tests/unit/db/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								glare/tests/unit/db/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										224
									
								
								glare/tests/unit/db/migrations/test_migrations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								glare/tests/unit/db/migrations/test_migrations.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | |||||||
|  | # 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. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | Tests for database migrations. There are "opportunistic" tests for both mysql | ||||||
|  | and postgresql in here, which allows testing against these databases in a | ||||||
|  | properly configured unit test environment. | ||||||
|  | For the opportunistic testing you need to set up a db named 'openstack_citest' | ||||||
|  | with user 'openstack_citest' and password 'openstack_citest' on localhost. | ||||||
|  | The test will then use that db and u/p combo to run the tests. | ||||||
|  | For postgres on Ubuntu this can be done with the following commands: | ||||||
|  | :: | ||||||
|  |  sudo -u postgres psql | ||||||
|  |  postgres=# create user openstack_citest with createdb login password | ||||||
|  |       'openstack_citest'; | ||||||
|  |  postgres=# create database openstack_citest with owner openstack_citest; | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import contextlib | ||||||
|  |  | ||||||
|  | from alembic import script | ||||||
|  | import mock | ||||||
|  | from oslo_db.sqlalchemy import test_base | ||||||
|  | from oslo_db.sqlalchemy import utils as db_utils | ||||||
|  | from oslo_log import log as logging | ||||||
|  | import sqlalchemy | ||||||
|  | import sqlalchemy.exc | ||||||
|  |  | ||||||
|  | from glare.db.migration import migration | ||||||
|  | import glare.db.sqlalchemy.api | ||||||
|  | from glare.i18n import _LE | ||||||
|  | from glare.tests.unit import glare_fixtures | ||||||
|  |  | ||||||
|  | LOG = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @contextlib.contextmanager | ||||||
|  | def patch_with_engine(engine): | ||||||
|  |     with mock.patch.object(glare.db.sqlalchemy.api, | ||||||
|  |                            'get_engine') as patch_engine: | ||||||
|  |         patch_engine.return_value = engine | ||||||
|  |         yield | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WalkVersionsMixin(object): | ||||||
|  |     def _walk_versions(self, engine=None, alembic_cfg=None): | ||||||
|  |         # Determine latest version script from the repo, then | ||||||
|  |         # upgrade from 1 through to the latest, with no data | ||||||
|  |         # in the databases. This just checks that the schema itself | ||||||
|  |         # upgrades successfully. | ||||||
|  |  | ||||||
|  |         # Place the database under version control | ||||||
|  |         with patch_with_engine(engine): | ||||||
|  |  | ||||||
|  |             script_directory = script.ScriptDirectory.from_config(alembic_cfg) | ||||||
|  |  | ||||||
|  |             self.assertIsNone(self.migration_api.version(engine)) | ||||||
|  |  | ||||||
|  |             versions = [ver for ver in script_directory.walk_revisions()] | ||||||
|  |  | ||||||
|  |             for version in reversed(versions): | ||||||
|  |                 with glare_fixtures.BannedDBSchemaOperations(): | ||||||
|  |                     self._migrate_up(engine, alembic_cfg, | ||||||
|  |                                      version.revision, with_data=True) | ||||||
|  |  | ||||||
|  |     def _migrate_up(self, engine, config, version, with_data=False): | ||||||
|  |         """migrate up to a new version of the db. | ||||||
|  |         We allow for data insertion and post checks at every | ||||||
|  |         migration version with special _pre_upgrade_### and | ||||||
|  |         _check_### functions in the main test. | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             if with_data: | ||||||
|  |                 data = None | ||||||
|  |                 pre_upgrade = getattr( | ||||||
|  |                     self, "_pre_upgrade_%s" % version, None) | ||||||
|  |                 if pre_upgrade: | ||||||
|  |                     data = pre_upgrade(engine) | ||||||
|  |  | ||||||
|  |             self.migration_api.upgrade(version, config=config) | ||||||
|  |             self.assertEqual(version, self.migration_api.version(engine)) | ||||||
|  |             if with_data: | ||||||
|  |                 check = getattr(self, "_check_%s" % version, None) | ||||||
|  |                 if check: | ||||||
|  |                     check(engine, data) | ||||||
|  |         except Exception: | ||||||
|  |             LOG.error(_LE("Failed to migrate to version %(version)s on engine " | ||||||
|  |                           "%(engine)s"), | ||||||
|  |                       {'version': version, 'engine': engine}) | ||||||
|  |             raise | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GlareMigrationsCheckers(object): | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super(GlareMigrationsCheckers, self).setUp() | ||||||
|  |         self.config = migration.get_alembic_config() | ||||||
|  |         self.migration_api = migration | ||||||
|  |  | ||||||
|  |     def assert_table(self, engine, table_name, indices, columns): | ||||||
|  |         table = db_utils.get_table(engine, table_name) | ||||||
|  |         index_data = [(index.name, index.columns.keys()) for index in | ||||||
|  |                       table.indexes] | ||||||
|  |         column_data = [column.name for column in table.columns] | ||||||
|  |         self.assertItemsEqual(columns, column_data) | ||||||
|  |         self.assertItemsEqual(indices, index_data) | ||||||
|  |  | ||||||
|  |     def test_walk_versions(self): | ||||||
|  |         self._walk_versions(self.engine, self.config) | ||||||
|  |  | ||||||
|  |     def _pre_upgrade_001(self, engine): | ||||||
|  |         self.assertRaises(sqlalchemy.exc.NoSuchTableError, | ||||||
|  |                           db_utils.get_table, engine, | ||||||
|  |                           'glare_artifacts') | ||||||
|  |         self.assertRaises(sqlalchemy.exc.NoSuchTableError, | ||||||
|  |                           db_utils.get_table, engine, | ||||||
|  |                           'glare_artifact_tags') | ||||||
|  |         self.assertRaises(sqlalchemy.exc.NoSuchTableError, | ||||||
|  |                           db_utils.get_table, engine, | ||||||
|  |                           'glare_artifact_properties') | ||||||
|  |         self.assertRaises(sqlalchemy.exc.NoSuchTableError, | ||||||
|  |                           db_utils.get_table, engine, | ||||||
|  |                           'glare_artifact_blobs') | ||||||
|  |  | ||||||
|  |     def _check_001(self, engine, data): | ||||||
|  |         artifacts_indices = [('ix_glare_artifact_name_and_version', | ||||||
|  |                               ['name', 'version_prefix', 'version_suffix']), | ||||||
|  |                              ('ix_glare_artifact_type', | ||||||
|  |                               ['type_name']), | ||||||
|  |                              ('ix_glare_artifact_status', ['status']), | ||||||
|  |                              ('ix_glare_artifact_visibility', ['visibility']), | ||||||
|  |                              ('ix_glare_artifact_owner', ['owner'])] | ||||||
|  |         artifacts_columns = ['id', | ||||||
|  |                              'name', | ||||||
|  |                              'type_name', | ||||||
|  |                              'version_prefix', | ||||||
|  |                              'version_suffix', | ||||||
|  |                              'version_meta', | ||||||
|  |                              'description', | ||||||
|  |                              'visibility', | ||||||
|  |                              'status', | ||||||
|  |                              'owner', | ||||||
|  |                              'created_at', | ||||||
|  |                              'updated_at', | ||||||
|  |                              'activated_at'] | ||||||
|  |         self.assert_table(engine, 'glare_artifacts', artifacts_indices, | ||||||
|  |                           artifacts_columns) | ||||||
|  |  | ||||||
|  |         tags_indices = [('ix_glare_artifact_tags_artifact_id', | ||||||
|  |                          ['artifact_id']), | ||||||
|  |                         ('ix_glare_artifact_tags_artifact_id_tag_value', | ||||||
|  |                          ['artifact_id', | ||||||
|  |                           'value'])] | ||||||
|  |         tags_columns = ['id', | ||||||
|  |                         'artifact_id', | ||||||
|  |                         'value'] | ||||||
|  |         self.assert_table(engine, 'glare_artifact_tags', tags_indices, | ||||||
|  |                           tags_columns) | ||||||
|  |  | ||||||
|  |         prop_indices = [ | ||||||
|  |             ('ix_glare_artifact_properties_artifact_id', | ||||||
|  |              ['artifact_id']), | ||||||
|  |             ('ix_glare_artifact_properties_name', ['name'])] | ||||||
|  |         prop_columns = ['id', | ||||||
|  |                         'artifact_id', | ||||||
|  |                         'name', | ||||||
|  |                         'string_value', | ||||||
|  |                         'int_value', | ||||||
|  |                         'numeric_value', | ||||||
|  |                         'bool_value', | ||||||
|  |                         'key_name', | ||||||
|  |                         'position'] | ||||||
|  |         self.assert_table(engine, 'glare_artifact_properties', prop_indices, | ||||||
|  |                           prop_columns) | ||||||
|  |  | ||||||
|  |         blobs_indices = [ | ||||||
|  |             ('ix_glare_artifact_blobs_artifact_id', ['artifact_id']), | ||||||
|  |             ('ix_glare_artifact_blobs_name', ['name'])] | ||||||
|  |         blobs_columns = ['id', | ||||||
|  |                          'artifact_id', | ||||||
|  |                          'size', | ||||||
|  |                          'checksum', | ||||||
|  |                          'name', | ||||||
|  |                          'key_name', | ||||||
|  |                          'external', | ||||||
|  |                          'status', | ||||||
|  |                          'content_type', | ||||||
|  |                          'url'] | ||||||
|  |         self.assert_table(engine, 'glare_artifact_blobs', blobs_indices, | ||||||
|  |                           blobs_columns) | ||||||
|  |  | ||||||
|  |         locks_indices = [] | ||||||
|  |         locks_columns = ['id'] | ||||||
|  |         self.assert_table(engine, 'glare_artifact_locks', locks_indices, | ||||||
|  |                           locks_columns) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestMigrationsMySQL(GlareMigrationsCheckers, | ||||||
|  |                           WalkVersionsMixin, | ||||||
|  |                           test_base.MySQLOpportunisticTestCase): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestMigrationsPostgreSQL(GlareMigrationsCheckers, | ||||||
|  |                                WalkVersionsMixin, | ||||||
|  |                                test_base.PostgreSQLOpportunisticTestCase): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestMigrationsSqlite(GlareMigrationsCheckers, | ||||||
|  |                            WalkVersionsMixin, | ||||||
|  |                            test_base.DbTestCase,): | ||||||
|  |     pass | ||||||
							
								
								
									
										40
									
								
								glare/tests/unit/glare_fixtures.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								glare/tests/unit/glare_fixtures.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | # Copyright (c) 2016 Mirantis, Inc. | ||||||
|  | # | ||||||
|  | # 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 fixtures | ||||||
|  |  | ||||||
|  | from glare.common import exception | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BannedDBSchemaOperations(fixtures.Fixture): | ||||||
|  |     """Ban some operations for migrations""" | ||||||
|  |     def __init__(self, banned_resources=None): | ||||||
|  |         super(BannedDBSchemaOperations, self).__init__() | ||||||
|  |         self._banned_resources = banned_resources or [] | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _explode(resource, op): | ||||||
|  |         raise exception.DBNotAllowed( | ||||||
|  |             'Operation %s.%s() is not allowed in a database migration' % ( | ||||||
|  |                 resource, op)) | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super(BannedDBSchemaOperations, self).setUp() | ||||||
|  |         for thing in self._banned_resources: | ||||||
|  |             self.useFixture(fixtures.MonkeyPatch( | ||||||
|  |                 'sqlalchemy.%s.drop' % thing, | ||||||
|  |                 lambda *a, **k: self._explode(thing, 'drop'))) | ||||||
|  |             self.useFixture(fixtures.MonkeyPatch( | ||||||
|  |                 'sqlalchemy.%s.alter' % thing, | ||||||
|  |                 lambda *a, **k: self._explode(thing, 'alter'))) | ||||||
							
								
								
									
										37
									
								
								glare/tests/unit/test_fixtures.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								glare/tests/unit/test_fixtures.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | # Copyright (c) 2016 Mirantis, Inc. | ||||||
|  | # | ||||||
|  | # 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 sqlalchemy | ||||||
|  | import testtools | ||||||
|  |  | ||||||
|  | from glare.common import exception | ||||||
|  | from glare.tests.unit import glare_fixtures | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestBannedDBSchemaOperations(testtools.TestCase): | ||||||
|  |     def test_column(self): | ||||||
|  |         column = sqlalchemy.Column() | ||||||
|  |         with glare_fixtures.BannedDBSchemaOperations(['Column']): | ||||||
|  |             self.assertRaises(exception.DBNotAllowed, | ||||||
|  |                               column.drop) | ||||||
|  |             self.assertRaises(exception.DBNotAllowed, | ||||||
|  |                               column.alter) | ||||||
|  |  | ||||||
|  |     def test_table(self): | ||||||
|  |         table = sqlalchemy.Table() | ||||||
|  |         with glare_fixtures.BannedDBSchemaOperations(['Table']): | ||||||
|  |             self.assertRaises(exception.DBNotAllowed, | ||||||
|  |                               table.drop) | ||||||
|  |             self.assertRaises(exception.DBNotAllowed, | ||||||
|  |                               table.alter) | ||||||
		Reference in New Issue
	
	Block a user
	 Mike Fedosin
					Mike Fedosin