Example #1
0
class MigrationPackage(object):
    """
    A wrapper around the migration packages found in pulp.server.db.migrations. Has methods to
    retrieve information about the migrations that are found there, and to apply the migrations.
    """
    class DuplicateVersions(Exception):
        """
        This is raised when a single migration package has two MigrationModules in it that have the
        same version.
        """
        pass

    class MissingVersion(Exception):
        """
        This is raised when a migration package has a gap in the MigrationModule versions.
        """
        pass

    def __init__(self, python_package):
        """
        Initialize the MigrationPackage to represent the Python migration package passed in.

        :param python_package: The Python package this object should represent
        :type  python_package: package
        """
        self._package = python_package
        # This is an object representation of the DB object that keeps track of the migration
        # version that has been applied

        try:
            self._migration_tracker = MigrationTracker.objects().get(name=self.name)
        except DoesNotExist:
            self._migration_tracker = MigrationTracker(name=self.name)
            self._migration_tracker.save()

        # Calculate the latest available version
        available_versions = self.available_versions
        if available_versions:
            self.latest_available_version = available_versions[-1]
        else:
            self.latest_available_version = 0

    def apply_migration(self, migration, update_current_version=True):
        """
        Apply the migration that is passed in, and update the DB to note the new version that this
        migration represents.

        :param migration:              The migration to apply
        :type  migration:              pulp.server.db.migrate.utils.MigrationModule
        :param update_current_version: If True, update the package's current version after
                                       successful application and enforce migration version order.
                                       If False, don't enforce and don't update.
        :type  update_current_version: bool
        """
        if update_current_version and migration.version != self.current_version + 1:
            msg = _('Cannot apply migration %(name)s, because the next migration version is '
                    '%(version)s.')
            msg = msg % {'name': migration.name, 'version': self.current_version + 1}
            raise Exception(msg)
        migration.migrate()
        if update_current_version:
            self._migration_tracker.version = migration.version
            self._migration_tracker.save()

    @property
    def available_versions(self):
        """
        Return a list of the migration versions that are available in this migration package.

        :rtype: list
        """
        migrations = self.migrations
        versions = [migration.version for migration in migrations]
        return versions

    @property
    def current_version(self):
        """
        An integer that represents the migration version that the database is currently at.
        None means that the migration package has never been run before.

        :rtype: int
        """
        return self._migration_tracker.version

    @property
    def migrations(self):
        """
        Finds all available migration modules for the MigrationPackage,
        and then sorts by the version. Return a list of MigrationModules.

        :rtype: list
        """
        # Generate a list of the names of the modules found inside this package
        module_names = [name for module_loader, name, ispkg in
                        iter_modules([os.path.dirname(self._package.__file__)])]
        migration_modules = []
        for module_name in module_names:
            try:
                module_name = '%s.%s' % (self.name, module_name)
                migration_modules.append(MigrationModule(module_name))
            except MigrationModule.MissingMigrate:
                msg = _("The module %(m)s doesn't have a migrate function. It will be ignored.")
                msg = msg % {'m': module_name}
                _logger.debug(msg)
            except MigrationModule.MissingVersion:
                msg = _("The module %(m)s doesn't conform to the migration package naming "
                        "conventions. It will be ignored.")
                msg = msg % {'m': module_name}
                _logger.debug(msg)
        migration_modules.sort()
        # We should have migrations starting at version 1, which each module version being exactly
        # one larger than the migration preceeding it.
        last_version = 0
        for module in migration_modules:
            if module.version == 0:
                error_message = _('0 is a reserved migration version number, but the '
                                  'module %(n)s has been assigned that version.')
                error_message = error_message % {'n': module.name}
                raise self.__class__.DuplicateVersions(error_message)
            if module.version == last_version:
                error_message = _('There are two migration modules that share version %(v)s in '
                                  '%(n)s.')
                error_message = error_message % {'v': module.version, 'n': self.name}
                raise self.__class__.DuplicateVersions(error_message)
            if module.version != last_version + 1:
                msg = _('Migration version %(v)s is missing in %(n)s.')
                msg = msg % ({'v': last_version + 1, 'n': self.name})
                raise self.__class__.MissingVersion(msg)
            last_version = module.version
        return migration_modules

    @property
    def name(self):
        """
        Returns the name of the Python package that this MigrationPackage represents.

        :rtype: str
        """
        return self._package.__name__

    @property
    def unapplied_migrations(self):
        """
        Return a list of MigrationModules in this package that have not been applied yet.

        :rtype: list
        """
        return [migration for migration in self.migrations
                if migration.version > self.current_version]

    def __cmp__(self, other_package):
        """
        This method returns a negative value if self.name < other_package.name, 0 if they are
        equal, and a positive value if self.name > other_package.name. There is an exception to this
        sorting rule, in that if self._package is pulp.server.db.migrations, this method will always
        return -1, and if other_package is pulp.server.db.migrations, it will always return 1.

        :rtype: int
        """
        if self._package is pulp.server.db.migrations:
            return -1
        if other_package._package is pulp.server.db.migrations:
            return 1
        return cmp(self.name, other_package.name)

    def __str__(self):
        return self.name

    def __repr__(self):
        return str(self)
Example #2
0
class MigrationPackage(object):
    """
    A wrapper around the migration packages found in pulp.server.db.migrations. Has methods to
    retrieve information about the migrations that are found there, and to apply the migrations.
    """

    class DuplicateVersions(Exception):
        """
        This is raised when a single migration package has two MigrationModules in it that have the
        same version.
        """

        pass

    class MissingVersion(Exception):
        """
        This is raised when a migration package has a gap in the MigrationModule versions.
        """

        pass

    def __init__(self, python_package):
        """
        Initialize the MigrationPackage to represent the Python migration package passed in.

        :param python_package: The Python package this object should represent
        :type  python_package: package
        """
        self._package = python_package
        # This is an object representation of the DB object that keeps track of the migration
        # version that has been applied

        try:
            self._migration_tracker = MigrationTracker.objects().get(name=self.name)
        except DoesNotExist:
            self._migration_tracker = MigrationTracker(name=self.name)
            self._migration_tracker.save()

        # Calculate the latest available version
        available_versions = self.available_versions
        if available_versions:
            self.latest_available_version = available_versions[-1]
        else:
            self.latest_available_version = 0

    def apply_migration(self, migration, update_current_version=True):
        """
        Apply the migration that is passed in, and update the DB to note the new version that this
        migration represents.

        :param migration:              The migration to apply
        :type  migration:              pulp.server.db.migrate.utils.MigrationModule
        :param update_current_version: If True, update the package's current version after
                                       successful application and enforce migration version order.
                                       If False, don't enforce and don't update.
        :type  update_current_version: bool
        """
        if update_current_version and migration.version != self.current_version + 1:
            msg = _("Cannot apply migration %(name)s, because the next migration version is " "%(version)s.")
            msg = msg % {"name": migration.name, "version": self.current_version + 1}
            raise Exception(msg)
        migration.migrate()
        if update_current_version:
            self._migration_tracker.version = migration.version
            self._migration_tracker.save()

    @property
    def available_versions(self):
        """
        Return a list of the migration versions that are available in this migration package.

        :rtype: list
        """
        migrations = self.migrations
        versions = [migration.version for migration in migrations]
        return versions

    @property
    def current_version(self):
        """
        An integer that represents the migration version that the database is currently at.
        None means that the migration package has never been run before.

        :rtype: int
        """
        return self._migration_tracker.version

    @property
    def migrations(self):
        """
        Finds all available migration modules for the MigrationPackage,
        and then sorts by the version. Return a list of MigrationModules.

        :rtype: list
        """
        # Generate a list of the names of the modules found inside this package
        module_names = [name for module_loader, name, ispkg in iter_modules([os.path.dirname(self._package.__file__)])]
        migration_modules = []
        for module_name in module_names:
            try:
                module_name = "%s.%s" % (self.name, module_name)
                migration_modules.append(MigrationModule(module_name))
            except MigrationModule.MissingMigrate:
                msg = _("The module %(m)s doesn't have a migrate function. It will be ignored.")
                msg = msg % {"m": module_name}
                _logger.debug(msg)
            except MigrationModule.MissingVersion:
                msg = _(
                    "The module %(m)s doesn't conform to the migration package naming "
                    "conventions. It will be ignored."
                )
                msg = msg % {"m": module_name}
                _logger.debug(msg)
        migration_modules.sort()
        # We should have migrations starting at version 1, which each module version being exactly
        # one larger than the migration preceeding it.
        last_version = 0
        for module in migration_modules:
            if module.version == 0:
                error_message = _(
                    "0 is a reserved migration version number, but the " "module %(n)s has been assigned that version."
                )
                error_message = error_message % {"n": module.name}
                raise self.__class__.DuplicateVersions(error_message)
            if module.version == last_version:
                error_message = _("There are two migration modules that share version %(v)s in " "%(n)s.")
                error_message = error_message % {"v": module.version, "n": self.name}
                raise self.__class__.DuplicateVersions(error_message)
            if module.version != last_version + 1:
                msg = _("Migration version %(v)s is missing in %(n)s.")
                msg = msg % ({"v": last_version + 1, "n": self.name})
                raise self.__class__.MissingVersion(msg)
            last_version = module.version
        return migration_modules

    @property
    def name(self):
        """
        Returns the name of the Python package that this MigrationPackage represents.

        :rtype: str
        """
        return self._package.__name__

    @property
    def unapplied_migrations(self):
        """
        Return a list of MigrationModules in this package that have not been applied yet.

        :rtype: list
        """
        return [migration for migration in self.migrations if migration.version > self.current_version]

    def __cmp__(self, other_package):
        """
        This method returns a negative value if self.name < other_package.name, 0 if they are
        equal, and a positive value if self.name > other_package.name. There is an exception to this
        sorting rule, in that if self._package is pulp.server.db.migrations, this method will always
        return -1, and if other_package is pulp.server.db.migrations, it will always return 1.

        :rtype: int
        """
        if self._package is pulp.server.db.migrations:
            return -1
        if other_package._package is pulp.server.db.migrations:
            return 1
        return cmp(self.name, other_package.name)

    def __str__(self):
        return self.name

    def __repr__(self):
        return str(self)