Add name scheme update script for alembic version table
The goal of this patch is to buil in the ability to upgrade the alembic version table of both new and existing RefStack databases in order to allow for the usage of subunit2sql tooling within the existing db, thus facilitating the upload and storage of subunit data in RefStack server. [TEST CASES HANDLED] * default name -> nondefault name * nondefault name -> default name * nondefault -> another nondefault [OTHER ACTION ITEMS] * tighten up workflow for cleaner code Change-Id: I7aa0965b22a46439d66c81108fc0b8947316579d
This commit is contained in:
parent
5f94bbe0d8
commit
3f3e80d7e6
@ -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():
|
||||||
|
@ -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)
|
||||||
|
127
refstack/db/migrations/alembic/utils.py
Normal file
127
refstack/db/migrations/alembic/utils.py
Normal 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)
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
2
tox.ini
2
tox.ini
@ -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]
|
||||||
|
Loading…
Reference in New Issue
Block a user