Add upgrade test coverage

Adding additional test upgrade coverage. This implementation
tests things like the is_migration_needed flag. In the future
we can also add support for MySQL functional coverage as well.

Change-Id: I9386d1bfbfee1fe8ff41859520cdbe94381ee3fd
This commit is contained in:
Erik Olof Gunnar Andersson 2023-12-11 05:23:36 -08:00
parent d90691f3a2
commit f5a034272d
4 changed files with 203 additions and 11 deletions

View File

@ -2,7 +2,7 @@
[alembic]
# path to migration scripts
script_location = alembic
script_location = %(here)s/alembic
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time

View File

@ -42,12 +42,12 @@ def run_migrations_offline() -> None:
script output.
"""
url = config.get_main_option("sqlalchemy.url")
url = config.get_main_option('sqlalchemy.url')
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
dialect_opts={'paramstyle': 'named'},
)
with context.begin_transaction():
@ -61,16 +61,32 @@ def run_migrations_online() -> None:
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
connectable = config.attributes.get('connection', None)
if connectable is None:
# only create Engine if we don't have a Connection from the outside
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=True,
transactional_ddl=True,
transaction_per_migration=True,
)
with connectable.connect() as connection:
with context.begin_transaction():
context.run_migrations()
else:
context.configure(
connection=connection, target_metadata=target_metadata,
transactional_ddl=True, transaction_per_migration=True
connection=connectable,
target_metadata=target_metadata,
render_as_batch=True,
transactional_ddl=True,
transaction_per_migration=True,
)
with context.begin_transaction():

View File

@ -0,0 +1,175 @@
# 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.
# Based on Nova's test_migrations.py
import os
from unittest import mock
from alembic import command as alembic_api
from alembic import config as alembic_config
from alembic import script as alembic_script
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import test_fixtures
from oslo_log import log as logging
from designate.storage import sqlalchemy
from designate.storage.sqlalchemy.alembic import legacy_utils
from designate import tests
LOG = logging.getLogger(__name__)
ALEMBIC_PATH = os.path.join(
os.path.dirname(sqlalchemy.__file__), 'alembic.ini'
)
class DesignateMigrationsWalk(
test_fixtures.OpportunisticDBTestMixin, tests.TestCase,
):
# Migrations can take a long time, particularly on underpowered CI nodes.
# Give them some breathing room.
TIMEOUT_SCALING_FACTOR = 4
def setUp(self):
super().setUp()
self.engine = enginefacade.writer.get_engine()
self.config = alembic_config.Config(ALEMBIC_PATH)
self.init_version = 'c9f427f7180a'
def _migrate_up(self, connection, revision):
if revision == self.init_version: # no tests for the initial revision
alembic_api.upgrade(self.config, revision)
return
self.assertIsNotNone(
getattr(self, '_check_%s' % revision, None),
(
'DB Migration %s does not have a test; you must add one'
) % revision,
)
pre_upgrade = getattr(self, '_pre_upgrade_%s' % revision, None)
if pre_upgrade:
pre_upgrade(connection)
alembic_api.upgrade(self.config, revision)
post_upgrade = getattr(self, '_check_%s' % revision, None)
if post_upgrade:
post_upgrade(connection)
def _check_867a331ce1fc(self, connection):
pass
def _check_d9a1883e93e9(self, connection):
pass
def _check_bfcfc4a07487(self, connection):
pass
def _check_f9f969f9d85e(self, connection):
pass
def _check_a69b45715cc1(self, connection):
pass
def _check_0bcf910ea823(self, connection):
pass
def _check_d04819112169(self, connection):
pass
def _check_304d41c3847a(self, connection):
pass
def _check_15b34ff3ecb8(self, connection):
pass
def _check_7977deaa5167(self, connection):
pass
def _check_93a00a815f07(self, connection):
pass
def _check_b8999fd10721(self, connection):
pass
def _check_91eb1eb7c882(self, connection):
pass
def _check_e5e2199ed76e(self, connection):
pass
def _check_b20189fd288e(self, connection):
pass
def _check_a005af3aa38e(self, connection):
pass
def test_single_base_revision(self):
script = alembic_script.ScriptDirectory.from_config(self.config)
self.assertEqual(1, len(script.get_bases()))
def test_walk_versions(self):
with self.engine.begin() as connection:
self.config.attributes['connection'] = connection
script = alembic_script.ScriptDirectory.from_config(self.config)
revisions = [x.revision for x in script.walk_revisions()]
# for some reason, 'walk_revisions' gives us the revisions in
# reverse chronological order, so we have to invert this
revisions.reverse()
self.assertEqual(revisions[0], self.init_version)
for revision in revisions:
LOG.info('Testing revision %s', revision)
self._migrate_up(connection, revision)
def test_is_migration_needed(self):
with self.engine.begin() as connection:
self.config.attributes['connection'] = connection
script = alembic_script.ScriptDirectory.from_config(self.config)
revisions = [x.revision for x in script.walk_revisions()]
# for some reason, 'walk_revisions' gives us the revisions in
# reverse chronological order, so we have to invert this
revisions.reverse()
self.assertEqual(revisions[0], self.init_version)
for revision in revisions:
LOG.info('Testing revision %s', revision)
# Let's stop after the first revision
# without is_migration_needed.
if revision == 'b20189fd288e':
break
self._migrate_up(connection, revision)
# Reset alembic.
alembic_api.stamp(self.config, None)
# We should only need the last few revisions.
with mock.patch.object(legacy_utils,
'is_migration_needed',
return_value=False):
for revision in revisions:
LOG.info('Testing revision %s', revision)
self._migrate_up(connection, revision)
class TestMigrationsWalkSQLite(
DesignateMigrationsWalk,
test_fixtures.OpportunisticDBTestMixin,
):
pass

View File

@ -19,3 +19,4 @@ zake>=0.1.6 # Apache-2.0
doc8>=0.6.0 # Apache-2.0
Pygments>=2.2.0 # BSD license
pymemcache!=1.3.0,>=1.2.9 # Apache 2.0 License
PyMySQL>=0.8.0 # MIT License