diff options
author | Victor Sergeyev <vsergeyev@mirantis.com> | 2013-06-06 11:05:41 +0300 |
---|---|---|
committer | Victor Sergeyev <vsergeyev@mirantis.com> | 2013-07-25 18:51:13 +0300 |
commit | cf41936300f1d37a4a684cc02ae7840d71eeb92c (patch) | |
tree | 662ca7a1274ef6ef1fc8010bff1ed9056802511b /openstack | |
parent | 8418dd3fba2f6f3ca4c635a26eeb61fd37d05f86 (diff) | |
download | oslo-cf41936300f1d37a4a684cc02ae7840d71eeb92c.tar.gz oslo-cf41936300f1d37a4a684cc02ae7840d71eeb92c.tar.xz oslo-cf41936300f1d37a4a684cc02ae7840d71eeb92c.zip |
Move sqlalchemy migration from Nova
Added functions for work with migration scripts to
openstack/common/db/sqlalchemy/migration.py file. This code can be used
in Nova, Cinder, Glance, etc. Added new DbMigrationError exception to
openstack/common/db/exception.py file.
Tests added.
blueprint test-migrations
Change-Id: Ia63f7fb89b0a6baacf3fd424c0db09c74680af4a
Diffstat (limited to 'openstack')
-rw-r--r-- | openstack/common/db/exception.py | 6 | ||||
-rw-r--r-- | openstack/common/db/sqlalchemy/migration.py | 119 |
2 files changed, 125 insertions, 0 deletions
diff --git a/openstack/common/db/exception.py b/openstack/common/db/exception.py index 69905da..3627de2 100644 --- a/openstack/common/db/exception.py +++ b/openstack/common/db/exception.py @@ -43,3 +43,9 @@ class DBDeadlock(DBError): class DBInvalidUnicodeParameter(Exception): message = _("Invalid Parameter: " "Unicode is not supported by the current database.") + + +class DbMigrationError(DBError): + """Wraps migration specific exception.""" + def __init__(self, message=None): + super(DbMigrationError, self).__init__(str(message)) diff --git a/openstack/common/db/sqlalchemy/migration.py b/openstack/common/db/sqlalchemy/migration.py index e643d8e..4154359 100644 --- a/openstack/common/db/sqlalchemy/migration.py +++ b/openstack/common/db/sqlalchemy/migration.py @@ -38,12 +38,53 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +import distutils.version as dist_version +import os import re +import migrate from migrate.changeset import ansisql from migrate.changeset.databases import sqlite +from migrate.versioning import util as migrate_util +import sqlalchemy from sqlalchemy.schema import UniqueConstraint +from openstack.common.db import exception +from openstack.common.db.sqlalchemy import session as db_session +from openstack.common.gettextutils import _ # noqa + + +@migrate_util.decorator +def patched_with_engine(f, *a, **kw): + url = a[0] + engine = migrate_util.construct_engine(url, **kw) + + try: + kw['engine'] = engine + return f(*a, **kw) + finally: + if isinstance(engine, migrate_util.Engine) and engine is not url: + migrate_util.log.debug('Disposing SQLAlchemy engine %s', engine) + engine.dispose() + + +# TODO(jkoelker) When migrate 0.7.3 is released and nova depends +# on that version or higher, this can be removed +MIN_PKG_VERSION = dist_version.StrictVersion('0.7.3') +if (not hasattr(migrate, '__version__') or + dist_version.StrictVersion(migrate.__version__) < MIN_PKG_VERSION): + migrate_util.with_engine = patched_with_engine + + +# NOTE(jkoelker) Delay importing migrate until we are patched +from migrate import exceptions as versioning_exceptions +from migrate.versioning import api as versioning_api +from migrate.versioning.repository import Repository + +_REPOSITORY = None + +get_engine = db_session.get_engine + def _get_unique_constraints(self, table): """Retrieve information about existing unique constraints of the table @@ -157,3 +198,81 @@ def patch_migrate(): _visit_migrate_unique_constraint constraint_cls.__bases__ = (ansisql.ANSIColumnDropper, sqlite.SQLiteConstraintGenerator) + + +def db_sync(abs_path, version=None, init_version=0): + """Upgrade or downgrade a database. + + Function runs the upgrade() or downgrade() functions in change scripts. + + :param abs_path: Absolute path to migrate repository. + :param version: Database will upgrade/downgrade until this version. + If None - database will update to the latest + available version. + :param init_version: Initial database version + """ + if version is not None: + try: + version = int(version) + except ValueError: + raise exception.DbMigrationError( + message=_("version should be an integer")) + + current_version = db_version(abs_path, init_version) + repository = _find_migrate_repo(abs_path) + if version is None or version > current_version: + return versioning_api.upgrade(get_engine(), repository, version) + else: + return versioning_api.downgrade(get_engine(), repository, + version) + + +def db_version(abs_path, init_version): + """Show the current version of the repository. + + :param abs_path: Absolute path to migrate repository + :param version: Initial database version + """ + repository = _find_migrate_repo(abs_path) + try: + return versioning_api.db_version(get_engine(), repository) + except versioning_exceptions.DatabaseNotControlledError: + meta = sqlalchemy.MetaData() + engine = get_engine() + meta.reflect(bind=engine) + tables = meta.tables + if len(tables) == 0: + db_version_control(abs_path, init_version) + return versioning_api.db_version(get_engine(), repository) + else: + # Some pre-Essex DB's may not be version controlled. + # Require them to upgrade using Essex first. + raise exception.DbMigrationError( + message=_("Upgrade DB using Essex release first.")) + + +def db_version_control(abs_path, version=None): + """Mark a database as under this repository's version control. + + Once a database is under version control, schema changes should + only be done via change scripts in this repository. + + :param abs_path: Absolute path to migrate repository + :param version: Initial database version + """ + repository = _find_migrate_repo(abs_path) + versioning_api.version_control(get_engine(), repository, version) + return version + + +def _find_migrate_repo(abs_path): + """Get the project's change script repository + + :param abs_path: Absolute path to migrate repository + """ + global _REPOSITORY + if not os.path.exists(abs_path): + raise exception.DbMigrationError("Path %s not found" % abs_path) + if _REPOSITORY is None: + _REPOSITORY = Repository(abs_path) + return _REPOSITORY |