summaryrefslogtreecommitdiffstats
path: root/openstack
diff options
context:
space:
mode:
authorVictor Sergeyev <vsergeyev@mirantis.com>2013-06-06 11:05:41 +0300
committerVictor Sergeyev <vsergeyev@mirantis.com>2013-07-25 18:51:13 +0300
commitcf41936300f1d37a4a684cc02ae7840d71eeb92c (patch)
tree662ca7a1274ef6ef1fc8010bff1ed9056802511b /openstack
parent8418dd3fba2f6f3ca4c635a26eeb61fd37d05f86 (diff)
downloadoslo-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.py6
-rw-r--r--openstack/common/db/sqlalchemy/migration.py119
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