Coverage for manila/tests/db/migrations/alembic/test_migration.py: 95%
105 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2010-2011 OpenStack, LLC
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""
17Tests for database migrations.
18"""
20from unittest import mock
22from alembic import script
23from oslo_db.sqlalchemy import enginefacade
24from oslo_db.sqlalchemy import test_fixtures
25from oslo_log import log
26from oslotest import base as test_base
27from sqlalchemy.sql import text
29from manila.db.migrations.alembic import migration
30from manila.tests.db.migrations.alembic import migrations_data_checks
31from manila.tests import utils as test_utils
33LOG = log.getLogger('manila.tests.test_migrations')
36class ManilaMigrationsCheckers(migrations_data_checks.DbMigrationsData):
37 """Test alembic migrations."""
39 def setUp(self):
40 super().setUp()
41 self.engine = enginefacade.writer.get_engine()
43 @property
44 def snake_walk(self):
45 return True
47 @property
48 def downgrade(self):
49 return True
51 @property
52 def INIT_VERSION(self):
53 pass
55 @property
56 def REPOSITORY(self):
57 pass
59 @property
60 def migration_api(self):
61 return migration
63 @property
64 def migrate_engine(self):
65 return self.engine
67 def _walk_versions(self, snake_walk=False, downgrade=True):
68 # Determine latest version script from the repo, then
69 # upgrade from 1 through to the latest, with no data
70 # in the databases. This just checks that the schema itself
71 # upgrades successfully.
73 # Place the database under version control
74 alembic_cfg = migration._alembic_config()
75 script_directory = script.ScriptDirectory.from_config(alembic_cfg)
77 self.assertIsNone(self.migration_api.version())
79 versions = [ver for ver in script_directory.walk_revisions()]
81 LOG.debug('latest version is %s', versions[0].revision)
83 for version in reversed(versions):
84 self._migrate_up(version.revision, with_data=True)
85 if snake_walk:
86 downgraded = self._migrate_down(
87 version, with_data=True)
88 if downgraded:
89 self._migrate_up(version.revision)
91 if downgrade:
92 for version in versions:
93 downgraded = self._migrate_down(version)
94 if snake_walk and downgraded:
95 self._migrate_up(version.revision)
96 self._migrate_down(version)
98 def _migrate_down(self, version, with_data=False):
99 try:
100 self.migration_api.downgrade(version.down_revision)
101 except NotImplementedError:
102 # NOTE(sirp): some migrations, namely release-level
103 # migrations, don't support a downgrade.
104 return False
106 self.assertEqual(version.down_revision, self.migration_api.version())
108 if with_data:
109 post_downgrade = getattr(
110 self, "_post_downgrade_%s" % version.revision, None)
111 if post_downgrade:
112 with self.engine.begin() as conn:
113 post_downgrade(conn)
115 return True
117 def _migrate_up(self, version, with_data=False):
118 """migrate up to a new version of the db.
120 We allow for data insertion and post checks at every
121 migration version with special _pre_upgrade_### and
122 _check_### functions in the main test.
123 """
124 # NOTE(sdague): try block is here because it's impossible to debug
125 # where a failed data migration happens otherwise
126 try:
127 if with_data:
128 data = None
129 pre_upgrade = getattr(
130 self, "_pre_upgrade_%s" % version, None)
131 if pre_upgrade:
132 with self.engine.begin() as conn:
133 data = pre_upgrade(conn)
135 self.migration_api.upgrade(version)
136 self.assertEqual(version, self.migration_api.version())
138 if with_data:
139 check = getattr(self, "_check_%s" % version, None)
140 if check:
141 with self.engine.begin() as conn:
142 check(conn, data)
143 except Exception as e:
144 LOG.error("Failed to migrate to version %(version)s on engine "
145 "%(engine)s. Exception while running the migration: "
146 "%(exception)s", {'version': version,
147 'engine': self.engine,
148 'exception': e})
149 raise
151 # NOTE(vponomaryov): set 12 minutes timeout for case of running it on
152 # very slow nodes/VMs. Note, that this test becomes slower with each
153 # addition of new DB migration. On fast nodes it can take about 5-10 secs
154 # having Mitaka set of migrations.
155 # 'pymysql' works much slower on slow nodes than 'psycopg2'. And such
156 # timeout mostly required for testing of 'mysql' backend.
157 @test_utils.set_timeout(720)
158 def test_walk_versions(self):
159 """Walks all version scripts for each tested database.
161 While walking, ensure that there are no errors in the version
162 scripts for each engine.
163 """
164 with mock.patch('manila.db.sqlalchemy.api.get_engine',
165 return_value=self.engine):
166 self._walk_versions(snake_walk=self.snake_walk,
167 downgrade=self.downgrade)
169 def test_single_branch(self):
170 alembic_cfg = migration._alembic_config()
171 script_directory = script.ScriptDirectory.from_config(alembic_cfg)
173 actual_result = script_directory.get_heads()
175 self.assertEqual(1, len(actual_result),
176 "Db migrations should have only one branch.")
179class TestManilaMigrationsMySQL(
180 ManilaMigrationsCheckers,
181 test_fixtures.OpportunisticDBTestMixin,
182 test_base.BaseTestCase,
183):
184 """Run migration tests on MySQL backend."""
185 FIXTURE = test_fixtures.MySQLOpportunisticFixture
187 @test_utils.set_timeout(300)
188 def test_mysql_innodb(self):
189 """Test that table creation on mysql only builds InnoDB tables."""
190 with mock.patch('manila.db.sqlalchemy.api.get_engine',
191 return_value=self.engine):
192 self._walk_versions(snake_walk=False, downgrade=False)
194 with self.engine.begin() as conn:
195 # sanity check
196 sanity_check = """SELECT count(*)
197 FROM information_schema.tables
198 WHERE table_schema = :database;"""
199 total = conn.execute(
200 text(sanity_check),
201 {"database": self.engine.url.database})
203 self.assertGreater(
204 total.scalar(), 0, "No tables found. Wrong schema?")
206 noninnodb_query = """
207 SELECT count(*)
208 FROM information_schema.TABLES
209 WHERE table_schema = :database
210 AND engine != 'InnoDB'
211 AND table_name != 'alembic_version';"""
213 count = conn.execute(
214 text(noninnodb_query),
215 {"database": self.engine.url.database}
216 ).scalar()
217 self.assertEqual(0, count, "%d non InnoDB tables created" % count)
220class TestManilaMigrationsPostgreSQL(
221 ManilaMigrationsCheckers,
222 test_fixtures.OpportunisticDBTestMixin,
223 test_base.BaseTestCase,
224):
225 """Run migration tests on PostgreSQL backend."""
226 FIXTURE = test_fixtures.PostgresqlOpportunisticFixture