Ejemplo n.º 1
0
    def _apply_migration(self, version, migration, skip=False):
        """
        Persist and apply a migration

        First create an in-progress version entry, apply the script, then
        finalize the entry as succeeded, failed or skipped.

        When `skip` is True, do everything but actually run the script, for
        example, when baselining instead of migrating.
        """

        self.logger.info('Advancing to version {}'.format(version))

        if skip:
            statements = []
            self.logger.info('Migration is marked for skipping, '
                             'not actually running script')
        else:
            statements = CqlSplitter.split(migration.content)

        version_uuid = self._create_version(version, migration)
        new_state = Migration.State.FAILED

        result = None
        try:
            if statements:
                self.logger.info('Executing migration - '
                                 '{} CQL statements'.format(len(statements)))

            # Set default keyspace so migrations don't need to refer to it
            # manually
            # Fixes https://github.com/Cobliteam/cassandra-migrate/issues/5
            self.session.execute('USE {};'.format(self.config.keyspace))

            for statement in statements:
                self.session.execute(statement)
        except Exception:
            self.logger.exception('Failed to execute migration')
            raise FailedMigration(version, migration.name)
        else:
            new_state = (Migration.State.SUCCEEDED if not skip
                         else Migration.State.SKIPPED)
        finally:
            self.logger.info('Finalizing migration version with '
                             'state {}'.format(new_state))
            result = self._execute(
                self._q(FINALIZE_DB_VERSION),
                (new_state, version_uuid, Migration.State.IN_PROGRESS))

        if not result or not result[0].applied:
            raise ConcurrentMigration(version, migration.name)
Ejemplo n.º 2
0
    def _apply_python_migration(self, version, migration):
        """
        Persist and apply a python migration

        First create an in-progress version entry, apply the script, then
        finalize the entry as succeeded, failed or skipped.
        """
        self.logger.info('Applying python script')

        try:
            mod, _ = os.path.splitext(os.path.basename(migration.path))
            migration_script = importlib.import_module(mod)
            migration_script.execute(self._session)
        except Exception:
            self.logger.exception('Failed to execute script')
            raise FailedMigration(version, migration.name)
Ejemplo n.º 3
0
    def _cleanup_stuck_in_progress(self, cur_versions):
        if not cur_versions:
            return

        last_version = cur_versions[-1]
        if last_version.state != Migration.State.IN_PROGRESS:
            return

        self.logger.warn(
            'Cleaning up previous migration stuck in progress '
            '(version {}): {}'.format(last_version.version, last_version.name))

        result = self._execute(
            self._q(DELETE_DB_VERSION),
            (last_version.id, Migration.State.IN_PROGRESS))

        if not result[0].applied:
            raise FailedMigration(last_version.version,
                                      last_version.name)
Ejemplo n.º 4
0
    def _apply_cql_migration(self, version, migration):
        """
        Persist and apply a cql migration

        First create an in-progress version entry, apply the script, then
        finalize the entry as succeeded, failed or skipped.
        """

        self.logger.info('Applying cql migration')

        statements = CqlSplitter.split(migration.content)

        try:
            if statements:
                self.logger.info('Executing migration with '
                                 '{} CQL statements'.format(len(statements)))

            for statement in statements:
                self.session.execute(statement)
        except Exception:
            raise FailedMigration(version, migration.name)
Ejemplo n.º 5
0
    def _apply_migration(self, version, migration, skip=False):
        """
        Persist and apply a migration

        When `skip` is True, do everything but actually run the script, for
        example, when baselining instead of migrating.
        """

        self.logger.info('Advancing to version {}'.format(version))

        version_uuid = self._create_version(version, migration)
        new_state = Migration.State.FAILED
        sys.path.append(self.config.migrations_path)

        result = None

        try:
            if skip:
                self.logger.info('Migration is marked for skipping, '
                                 'not actually running script')
            else:
                if migration.is_python:
                    self._apply_python_migration(version, migration)
                else:
                    self._apply_cql_migration(version, migration)
        except Exception:
            self.logger.exception('Failed to execute migration')
            raise FailedMigration(version, migration.name)
        else:
            new_state = (Migration.State.SUCCEEDED
                         if not skip else Migration.State.SKIPPED)
        finally:
            self.logger.info('Finalizing migration version with '
                             'state {}'.format(new_state))
            result = self._execute(
                self._q(FINALIZE_DB_VERSION),
                (new_state, version_uuid, Migration.State.IN_PROGRESS))

        if not result or not result[0].applied:
            raise ConcurrentMigration(version, migration.name)
Ejemplo n.º 6
0
    def _verify_migrations(self,
                           migrations,
                           ignore_failed=False,
                           ignore_concurrent=False):
        """Verify if the version history persisted in C* matches the migrations

        Migrations with corresponding DB versions must have the same content
        and name.
        Every DB version must have a corresponding migration.
        Migrations without corresponding DB versions are considered pending,
        and returned as a result.

        Returns a list of tuples of (version, migration), with version starting
        from 1 and incrementing linearly.
        """

        # Load all the currently existing versions and sort them by version
        # number, as Cassandra can only sort it for us by partition.
        cur_versions = self._execute(
            self._q('SELECT * FROM "{keyspace}"."{table}"'))
        cur_versions = sorted(cur_versions, key=lambda v: v.version)

        last_version = None
        version_pairs = zip_longest(cur_versions, migrations)

        # Work through ordered pairs of (existing version, migration), so that
        # stored versions and expected migrations can be compared for any
        # differences.
        for i, (version, migration) in enumerate(version_pairs, 1):
            # If version is empty, the migration has not yet been applied.
            # Keep track of the first such version, and append it to the
            # pending migrations list.
            if not version:
                break

            # If migration is empty, we have a version in the database with
            # no corresponding file. That might mean we're running the wrong
            # migration or have an out-of-date state, so we must fail.
            if not migration:
                raise UnknownMigration(version.version, version.name)

            # A migration was previously run and failed.
            if version.state == Migration.State.FAILED:
                if ignore_failed:
                    break

                raise FailedMigration(version.version, version.name)

            last_version = version.version

            # A stored version's migrations differs from the one in the FS.
            if version.content != migration.content or \
               version.name != migration.name or \
               bytearray(version.checksum) != bytearray(migration.checksum):
                raise InconsistentState(migration, version)

        if not last_version:
            pending_migrations = list(migrations)
        else:
            pending_migrations = list(migrations)[last_version:]

        if not pending_migrations:
            self.logger.info('Database is already up-to-date')
        else:
            self.logger.info('Pending migrations found. Current version: {}, '
                             'Latest version: {}'.format(
                                 last_version, len(migrations)))

        pending_migrations = enumerate(pending_migrations,
                                       (last_version or 0) + 1)
        return last_version, cur_versions, list(pending_migrations)