Merge "Add name scheme update script for alembic version table"

This commit is contained in:
Zuul 2017-10-17 17:22:54 +00:00 committed by Gerrit Code Review
commit fab8d8a785
6 changed files with 150 additions and 24 deletions

View File

@ -19,10 +19,13 @@
from __future__ import with_statement from __future__ import with_statement
from alembic import context from alembic import context
from oslo_config import cfg
from refstack.db.sqlalchemy import api as db_api from refstack.db.sqlalchemy import api as db_api
from refstack.db.sqlalchemy import models as db_models from refstack.db.sqlalchemy import models as db_models
CONF = cfg.CONF
def run_migrations_online(): def run_migrations_online():
"""Run migrations in 'online' mode. """Run migrations in 'online' mode.
@ -33,9 +36,9 @@ def run_migrations_online():
engine = db_api.get_engine() engine = db_api.get_engine()
connection = engine.connect() connection = engine.connect()
target_metadata = db_models.RefStackBase.metadata target_metadata = db_models.RefStackBase.metadata
context.configure( context.configure(connection=connection,
connection=connection, target_metadata=target_metadata,
target_metadata=target_metadata) version_table=getattr(CONF, 'version_table'))
try: try:
with context.begin_transaction(): with context.begin_transaction():

View File

@ -13,24 +13,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Implementation of Alembic commands.""" """Implementation of Alembic commands."""
import os
import alembic import alembic
from alembic import config as alembic_config
import alembic.migration as alembic_migration import alembic.migration as alembic_migration
from oslo_config import cfg from oslo_config import cfg
from refstack.db.sqlalchemy import api as db_api from refstack.db.sqlalchemy import api as db_api
from refstack.db.migrations.alembic import utils
CONF = cfg.CONF CONF = cfg.CONF
def _alembic_config():
path = os.path.join(os.path.dirname(__file__), os.pardir, 'alembic.ini')
config = alembic_config.Config(path)
return config
def version(): def version():
"""Current database version. """Current database version.
@ -39,7 +30,10 @@ def version():
""" """
engine = db_api.get_engine() engine = db_api.get_engine()
with engine.connect() as conn: with engine.connect() as conn:
context = alembic_migration.MigrationContext.configure(conn) conf_table = getattr(CONF, 'version_table')
utils.recheck_alembic_table(conn)
context = alembic_migration.MigrationContext.configure(
conn, opts={'version_table': conf_table})
return context.get_current_revision() return context.get_current_revision()
@ -49,7 +43,7 @@ def upgrade(revision):
:param version: Desired database version :param version: Desired database version
:type version: string :type version: string
""" """
return alembic.command.upgrade(_alembic_config(), revision or 'head') return alembic.command.upgrade(utils.alembic_config(), revision or 'head')
def downgrade(revision): def downgrade(revision):
@ -58,7 +52,8 @@ def downgrade(revision):
:param version: Desired database version :param version: Desired database version
:type version: string :type version: string
""" """
return alembic.command.downgrade(_alembic_config(), revision or 'base') return alembic.command.downgrade(utils.alembic_config(),
revision or 'base')
def stamp(revision): def stamp(revision):
@ -70,7 +65,7 @@ def stamp(revision):
database with most recent revision database with most recent revision
:type revision: string :type revision: string
""" """
return alembic.command.stamp(_alembic_config(), revision or 'head') return alembic.command.stamp(utils.alembic_config(), revision or 'head')
def revision(message=None, autogenerate=False): def revision(message=None, autogenerate=False):
@ -82,4 +77,5 @@ def revision(message=None, autogenerate=False):
state state
:type autogenerate: bool :type autogenerate: bool
""" """
return alembic.command.revision(_alembic_config(), message, autogenerate) return alembic.command.revision(utils.alembic_config(),
message, autogenerate)

View File

@ -0,0 +1,127 @@
# Copyright (c) 2015 Mirantis, 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.
"""Utilities used in the implementation of Alembic commands."""
import os
from alembic import config as alembic_conf
from alembic.operations import Operations
import alembic.migration as alembic_migration
from collections import Iterable
from oslo_config import cfg
from sqlalchemy import text
CONF = cfg.CONF
def alembic_config():
"""Initialize config objext from .ini file.
:returns: config object.
:type: object
"""
path = os.path.join(os.path.dirname(__file__), os.pardir, 'alembic.ini')
config = alembic_conf.Config(path)
return config
def get_table_version(conn, version_table_name):
"""Get table version.
:param engine: Initialized alembic engine object.
:param version_table_name: Version table name to check.
:type engine: object
:type version_table_name: string
:returns: string
"""
if not version_table_name:
return None
context = alembic_migration.MigrationContext.configure(
conn, opts={'version_table': version_table_name})
return context.get_current_revision()
def get_db_tables(conn):
"""Get current and default table values from the db.
:param engine: Initialized alembic engine object.
:type engine: object
:returns: tuple
"""
query = text("SELECT TABLE_NAME from information_schema.tables\
WHERE TABLE_NAME\
LIKE '%alembic_version%'\
AND table_schema = 'refstack'")
context = alembic_migration.MigrationContext.configure(conn)
op = Operations(context)
connection = op.get_bind()
search = connection.execute(query)
result = search.fetchall()
if isinstance(result, Iterable):
result = [table[0] for table in result]
else:
result = None
# if there is more than one version table, modify the
# one that does not have the default name, because subunit2sql uses the
# default name.
if result:
current_name =\
next((table for table in result if table != "alembic_version"),
result[0])
current_name = current_name.decode('utf-8')
current_version = get_table_version(conn, current_name)
default_name =\
next((table for table in result
if table == "alembic_version"), None)
default_version = get_table_version(conn, default_name)
if len(result) > 1 and not current_version:
if not default_name:
# this is the case where there is more than one
# nonstandard-named alembic table, and no default
current_name = next((table for table in result
if table != current_name),
result[0])
current_name = current_name.decode('utf-8')
elif current_name:
# this is the case where the current-named table
# exists, but is empty
current_name = default_name
current_version = default_version
current_table = (current_name, current_version)
default_table = (default_name, default_version)
else:
default_table = (None, None)
current_table = default_table
return current_table, default_table
def recheck_alembic_table(conn):
"""check and update alembic version table.
Should check current alembic version table against conf and rename the
existing table if the two values don't match.
"""
conf_table = getattr(CONF, 'version_table')
conf_table_version = get_table_version(conn, conf_table)
current_table, default_table = get_db_tables(conn)
if current_table[0]:
if current_table[0] != conf_table:
context = alembic_migration.MigrationContext.configure(conn)
op = Operations(context)
if conf_table and not conf_table_version:
# make sure there is not present-but-empty table
# that will prevent us from renaming the current table
op.drop_table(conf_table)
op.rename_table(current_table[0], conf_table)

View File

@ -20,7 +20,7 @@ import mock
from oslotest import base from oslotest import base
from refstack.db import migration from refstack.db import migration
from refstack.db.migrations.alembic import migration as alembic_migration from refstack.db.migrations.alembic import utils
class AlembicConfigTestCase(base.BaseTestCase): class AlembicConfigTestCase(base.BaseTestCase):
@ -30,7 +30,7 @@ class AlembicConfigTestCase(base.BaseTestCase):
def test_alembic_config(self, os_join, alembic_config): def test_alembic_config(self, os_join, alembic_config):
os_join.return_value = 'fake_path' os_join.return_value = 'fake_path'
alembic_config.return_value = 'fake_config' alembic_config.return_value = 'fake_config'
result = alembic_migration._alembic_config() result = utils.alembic_config()
self.assertEqual(result, 'fake_config') self.assertEqual(result, 'fake_config')
alembic_config.assert_called_once_with('fake_path') alembic_config.assert_called_once_with('fake_path')
@ -41,7 +41,7 @@ class MigrationTestCase(base.BaseTestCase):
def setUp(self): def setUp(self):
super(MigrationTestCase, self).setUp() super(MigrationTestCase, self).setUp()
self.config_patcher = mock.patch( self.config_patcher = mock.patch(
'refstack.db.migrations.alembic.migration._alembic_config') 'refstack.db.migrations.alembic.utils.alembic_config')
self.config = self.config_patcher.start() self.config = self.config_patcher.start()
self.config.return_value = 'fake_config' self.config.return_value = 'fake_config'
self.addCleanup(self.config_patcher.stop) self.addCleanup(self.config_patcher.stop)
@ -57,7 +57,7 @@ class MigrationTestCase(base.BaseTestCase):
engine.connect = mock.MagicMock() engine.connect = mock.MagicMock()
get_engine.return_value = engine get_engine.return_value = engine
migration.version() migration.version()
context.get_current_revision.assert_called_once_with() context.get_current_revision.assert_called_with()
engine.connect.assert_called_once_with() engine.connect.assert_called_once_with()
@mock.patch('alembic.command.upgrade') @mock.patch('alembic.command.upgrade')

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
ALLOWED_EXTRA_MISSING=4 ALLOWED_EXTRA_MISSING=30
show_diff () { show_diff () {
head -1 $1 head -1 $1

View File

@ -57,7 +57,7 @@ commands = {posargs}
[testenv:gen-cover] [testenv:gen-cover]
commands = python setup.py testr --coverage \ commands = python setup.py testr --coverage \
--omit='{toxinidir}/refstack/tests*,{toxinidir}/refstack/api/config.py,{toxinidir}/refstack/db/migrations/alembic/env.py,{toxinidir}/refstack/opts.py' \ --omit='{toxinidir}/refstack/tests*,{toxinidir}/refstack/api/config.py,{toxinidir}/refstack/db/migrations/alembic/*,{toxinidir}/refstack/opts.py' \
--testr-args='{posargs}' --testr-args='{posargs}'
[testenv:cover] [testenv:cover]