# Copyright 2013 Hewlett-Packard Development Company, L.P. # 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. """ Run storage database migration. """ from __future__ import print_function import sys from iotronic.common import context from iotronic.common.i18n import _ from iotronic.common import service from iotronic.db import api as db_api from iotronic.db import migration from oslo_config import cfg CONF = cfg.CONF dbapi = db_api.get_instance() # NOTE(rloo): This is a list of functions to perform online data migrations # (from previous releases) for this release, in batches. It may be empty. # The migration functions should be ordered by execution order; from earlier # to later releases. # # Each migration function takes two arguments -- the context and maximum # number of objects to migrate, and returns a 2-tuple -- the total number of # objects that need to be migrated at the beginning of the function, and the # number migrated. If the function determines that no migrations are needed, # it returns (0, 0). # # Example of a function docstring: # # def sample_data_migration(context, max_count): # """Sample method to migrate data to new format. # # :param context: an admin context # :param max_count: The maximum number of objects to migrate. Must be # >= 0. If zero, all the objects will be migrated. # :returns: A 2-tuple -- the total number of objects that need to be # migrated (at the beginning of this call) and the number # of migrated objects. # """ # NOTE(vdrok): Do not access objects' attributes, instead only provide object # and attribute name tuples, so that not to trigger the load of the whole # object, in case it is lazy loaded. The attribute will be accessed when needed # by doing getattr on the object ONLINE_MIGRATIONS = ( ) class DBCommand(object): def _check_versions(self): """Check the versions of objects. Check that the object versions are compatible with this release of iotronic. It does this by comparing the objects' .version field in the database, with the expected versions of these objects. If it isn't compatible, we exit the program, returning 2. """ if migration.version() is None: # no tables, nothing to check return """ try: if not dbapi.check_versions(): sys.stderr.write( _('The database is not compatible with this ' 'release of iotronic (%s). Please run ' '"iotronic-dbsync online_data_migrations" using ' 'the previous release.\n') % version.version_info.release_string()) # NOTE(rloo): We return 1 in online_data_migrations() to # indicate that there are more objects to migrate, # so don't use 1 here. sys.exit(2) except exception.DatabaseVersionTooOld: sys.stderr.write( _('The database version is not compatible with this ' 'release of iotronic (%s). This can happen if you are ' 'attempting to upgrade from a version older than ' 'the previous release (skip versions upgrade). ' 'This is an unsupported upgrade method. ' 'Please run "iotronic-dbsync upgrade" using the previous ' 'releases for a fast-forward upgrade.\n') % version.version_info.release_string()) sys.exit(2) """ def upgrade(self): self._check_versions() migration.upgrade(CONF.command.revision) def revision(self): migration.revision(CONF.command.message, CONF.command.autogenerate) def stamp(self): migration.stamp(CONF.command.revision) def version(self): print(migration.version()) def create_schema(self): migration.create_schema() def online_data_migrations(self): self._check_versions() self._run_online_data_migrations(max_count=CONF.command.max_count, options=CONF.command.options) def _run_migration_functions(self, context, max_count, options): """Runs the migration functions. Runs the data migration functions in the ONLINE_MIGRATIONS list. It makes sure the total number of object migrations doesn't exceed the specified max_count. A migration of an object will typically migrate one row of data inside the database. :param: context: an admin context :param: max_count: the maximum number of objects (rows) to migrate; a value >= 1. :param: options: migration options - dict mapping migration name to a dictionary of options for this migration. :raises: Exception from the migration function :returns: Boolean value indicating whether migrations are done. Returns False if max_count objects have been migrated (since at that point, it is unknown whether all migrations are done). Returns True if migrations are all done (i.e. fewer than max_count objects were migrated when the migrations are done). """ total_migrated = 0 for migration_func_obj, migration_func_name in ONLINE_MIGRATIONS: migration_func = getattr(migration_func_obj, migration_func_name) migration_opts = options.get(migration_func_name, {}) num_to_migrate = max_count - total_migrated try: total_to_do, num_migrated = migration_func(context, num_to_migrate, **migration_opts) except Exception as e: print(_("Error while running %(migration)s: %(err)s.") % {'migration': migration_func.__name__, 'err': e}, file=sys.stderr) raise print(_('%(migration)s() migrated %(done)i of %(total)i objects.') % {'migration': migration_func.__name__, 'total': total_to_do, 'done': num_migrated}) total_migrated += num_migrated if total_migrated >= max_count: # NOTE(rloo). max_count objects have been migrated so we have # to stop. We return False because there is no look-ahead so # we don't know if the migrations have been all done. All we # know is that we've migrated max_count. It is possible that # the migrations are done and that there aren't any more to # migrate after this, but that would involve checking: # 1. num_migrated == total_to_do (easy enough), AND # 2. whether there are other migration functions and whether # they need to do any object migrations (not so easy to # check) return False return True def _run_online_data_migrations(self, max_count=None, options=None): """Perform online data migrations for the release. Online data migrations are done by running all the data migration functions in the ONLINE_MIGRATIONS list. If max_count is None, all the functions will be run in batches of 50 objects, until the migrations are done. Otherwise, this will run (some of) the functions until max_count objects have been migrated. :param: max_count: the maximum number of individual object migrations or modified rows, a value >= 1. If None, migrations are run in a loop in batches of 50, until completion. :param: options: options to pass to migrations. List of values in the form of .