summaryrefslogtreecommitdiff
path: root/oslo_db/sqlalchemy/migration_cli
diff options
context:
space:
mode:
Diffstat (limited to 'oslo_db/sqlalchemy/migration_cli')
-rw-r--r--oslo_db/sqlalchemy/migration_cli/README.rst9
-rw-r--r--oslo_db/sqlalchemy/migration_cli/__init__.py0
-rw-r--r--oslo_db/sqlalchemy/migration_cli/ext_alembic.py78
-rw-r--r--oslo_db/sqlalchemy/migration_cli/ext_base.py79
-rw-r--r--oslo_db/sqlalchemy/migration_cli/ext_migrate.py69
-rw-r--r--oslo_db/sqlalchemy/migration_cli/manager.py71
6 files changed, 306 insertions, 0 deletions
diff --git a/oslo_db/sqlalchemy/migration_cli/README.rst b/oslo_db/sqlalchemy/migration_cli/README.rst
new file mode 100644
index 0000000..ebbbdcb
--- /dev/null
+++ b/oslo_db/sqlalchemy/migration_cli/README.rst
@@ -0,0 +1,9 @@
+This module could be used either for:
+1. Smooth transition from migrate tool to alembic
+2. As standalone alembic tool
+
+Core points:
+1. Upgrade/downgrade database with usage of alembic/migrate migrations
+or both
+2. Compatibility with oslo.config
+3. The way to autogenerate new revisions or stamps
diff --git a/oslo_db/sqlalchemy/migration_cli/__init__.py b/oslo_db/sqlalchemy/migration_cli/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/oslo_db/sqlalchemy/migration_cli/__init__.py
diff --git a/oslo_db/sqlalchemy/migration_cli/ext_alembic.py b/oslo_db/sqlalchemy/migration_cli/ext_alembic.py
new file mode 100644
index 0000000..243ae47
--- /dev/null
+++ b/oslo_db/sqlalchemy/migration_cli/ext_alembic.py
@@ -0,0 +1,78 @@
+# 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.
+
+import os
+
+import alembic
+from alembic import config as alembic_config
+import alembic.migration as alembic_migration
+
+from oslo_db.sqlalchemy.migration_cli import ext_base
+from oslo_db.sqlalchemy import session as db_session
+
+
+class AlembicExtension(ext_base.MigrationExtensionBase):
+
+ order = 2
+
+ @property
+ def enabled(self):
+ return os.path.exists(self.alembic_ini_path)
+
+ def __init__(self, migration_config):
+ """Extension to provide alembic features.
+
+ :param migration_config: Stores specific configuration for migrations
+ :type migration_config: dict
+ """
+ self.alembic_ini_path = migration_config.get('alembic_ini_path', '')
+ self.config = alembic_config.Config(self.alembic_ini_path)
+ # option should be used if script is not in default directory
+ repo_path = migration_config.get('alembic_repo_path')
+ if repo_path:
+ self.config.set_main_option('script_location', repo_path)
+ self.db_url = migration_config['db_url']
+
+ def upgrade(self, version):
+ return alembic.command.upgrade(self.config, version or 'head')
+
+ def downgrade(self, version):
+ if isinstance(version, int) or version is None or version.isdigit():
+ version = 'base'
+ return alembic.command.downgrade(self.config, version)
+
+ def version(self):
+ engine = db_session.create_engine(self.db_url)
+ with engine.connect() as conn:
+ context = alembic_migration.MigrationContext.configure(conn)
+ return context.get_current_revision()
+
+ def revision(self, message='', autogenerate=False):
+ """Creates template for migration.
+
+ :param message: Text that will be used for migration title
+ :type message: string
+ :param autogenerate: If True - generates diff based on current database
+ state
+ :type autogenerate: bool
+ """
+ return alembic.command.revision(self.config, message=message,
+ autogenerate=autogenerate)
+
+ def stamp(self, revision):
+ """Stamps database with provided revision.
+
+ :param revision: Should match one from repository or head - to stamp
+ database with most recent revision
+ :type revision: string
+ """
+ return alembic.command.stamp(self.config, revision=revision)
diff --git a/oslo_db/sqlalchemy/migration_cli/ext_base.py b/oslo_db/sqlalchemy/migration_cli/ext_base.py
new file mode 100644
index 0000000..205b7b2
--- /dev/null
+++ b/oslo_db/sqlalchemy/migration_cli/ext_base.py
@@ -0,0 +1,79 @@
+# 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.
+
+import abc
+
+import six
+
+
+@six.add_metaclass(abc.ABCMeta)
+class MigrationExtensionBase(object):
+
+ # used to sort migration in logical order
+ order = 0
+
+ @property
+ def enabled(self):
+ """Used for availability verification of a plugin.
+
+ :rtype: bool
+ """
+ return False
+
+ @abc.abstractmethod
+ def upgrade(self, version):
+ """Used for upgrading database.
+
+ :param version: Desired database version
+ :type version: string
+ """
+
+ @abc.abstractmethod
+ def downgrade(self, version):
+ """Used for downgrading database.
+
+ :param version: Desired database version
+ :type version: string
+ """
+
+ @abc.abstractmethod
+ def version(self):
+ """Current database version.
+
+ :returns: Databse version
+ :rtype: string
+ """
+
+ def revision(self, *args, **kwargs):
+ """Used to generate migration script.
+
+ In migration engines that support this feature, it should generate
+ new migration script.
+
+ Accept arbitrary set of arguments.
+ """
+ raise NotImplementedError()
+
+ def stamp(self, *args, **kwargs):
+ """Stamps database based on plugin features.
+
+ Accept arbitrary set of arguments.
+ """
+ raise NotImplementedError()
+
+ def __cmp__(self, other):
+ """Used for definition of plugin order.
+
+ :param other: MigrationExtensionBase instance
+ :rtype: bool
+ """
+ return self.order > other.order
diff --git a/oslo_db/sqlalchemy/migration_cli/ext_migrate.py b/oslo_db/sqlalchemy/migration_cli/ext_migrate.py
new file mode 100644
index 0000000..e31ee3d
--- /dev/null
+++ b/oslo_db/sqlalchemy/migration_cli/ext_migrate.py
@@ -0,0 +1,69 @@
+# 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.
+
+import logging
+import os
+
+from oslo_db._i18n import _LE
+from oslo_db.sqlalchemy import migration
+from oslo_db.sqlalchemy.migration_cli import ext_base
+from oslo_db.sqlalchemy import session as db_session
+
+
+LOG = logging.getLogger(__name__)
+
+
+class MigrateExtension(ext_base.MigrationExtensionBase):
+ """Extension to provide sqlalchemy-migrate features.
+
+ :param migration_config: Stores specific configuration for migrations
+ :type migration_config: dict
+ """
+
+ order = 1
+
+ def __init__(self, migration_config):
+ self.repository = migration_config.get('migration_repo_path', '')
+ self.init_version = migration_config.get('init_version', 0)
+ self.db_url = migration_config['db_url']
+ self.engine = db_session.create_engine(self.db_url)
+
+ @property
+ def enabled(self):
+ return os.path.exists(self.repository)
+
+ def upgrade(self, version):
+ version = None if version == 'head' else version
+ return migration.db_sync(
+ self.engine, self.repository, version,
+ init_version=self.init_version)
+
+ def downgrade(self, version):
+ try:
+ # version for migrate should be valid int - else skip
+ if version in ('base', None):
+ version = self.init_version
+ version = int(version)
+ return migration.db_sync(
+ self.engine, self.repository, version,
+ init_version=self.init_version)
+ except ValueError:
+ LOG.error(
+ _LE('Migration number for migrate plugin must be valid '
+ 'integer or empty, if you want to downgrade '
+ 'to initial state')
+ )
+ raise
+
+ def version(self):
+ return migration.db_version(
+ self.engine, self.repository, init_version=self.init_version)
diff --git a/oslo_db/sqlalchemy/migration_cli/manager.py b/oslo_db/sqlalchemy/migration_cli/manager.py
new file mode 100644
index 0000000..c8ab30e
--- /dev/null
+++ b/oslo_db/sqlalchemy/migration_cli/manager.py
@@ -0,0 +1,71 @@
+# 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.
+
+from stevedore import enabled
+
+
+MIGRATION_NAMESPACE = 'oslo.db.migration'
+
+
+def check_plugin_enabled(ext):
+ """Used for EnabledExtensionManager."""
+ return ext.obj.enabled
+
+
+class MigrationManager(object):
+
+ def __init__(self, migration_config):
+ self._manager = enabled.EnabledExtensionManager(
+ MIGRATION_NAMESPACE,
+ check_plugin_enabled,
+ invoke_kwds={'migration_config': migration_config},
+ invoke_on_load=True
+ )
+ if not self._plugins:
+ raise ValueError('There must be at least one plugin active.')
+
+ @property
+ def _plugins(self):
+ return sorted(ext.obj for ext in self._manager.extensions)
+
+ def upgrade(self, revision):
+ """Upgrade database with all available backends."""
+ results = []
+ for plugin in self._plugins:
+ results.append(plugin.upgrade(revision))
+ return results
+
+ def downgrade(self, revision):
+ """Downgrade database with available backends."""
+ # downgrading should be performed in reversed order
+ results = []
+ for plugin in reversed(self._plugins):
+ results.append(plugin.downgrade(revision))
+ return results
+
+ def version(self):
+ """Return last version of db."""
+ last = None
+ for plugin in self._plugins:
+ version = plugin.version()
+ if version:
+ last = version
+ return last
+
+ def revision(self, message, autogenerate):
+ """Generate template or autogenerated revision."""
+ # revision should be done only by last plugin
+ return self._plugins[-1].revision(message, autogenerate)
+
+ def stamp(self, revision):
+ """Create stamp for a given revision."""
+ return self._plugins[-1].stamp(revision)