Пример #1
0
    def test_not_upgraded_current_schema_version_with_outstanding_deltas(self):
        """
        Test that workers don't start if the DB is on the current schema version,
        but there are still outstanding delta migrations to run.
        """
        db_pool = self.hs.get_datastore().db_pool
        db_conn = LoggingDatabaseConnection(
            db_pool._db_pool.connect(),
            db_pool.engine,
            "tests",
        )

        # Set the schema version of the database to the current version
        cur = db_conn.cursor()
        cur.execute("UPDATE schema_version SET version = ?",
                    (SCHEMA_VERSION, ))

        db_conn.commit()

        # Path `os.listdir` here to make synapse think that there is a migration
        # file ready to be run.
        # Note that we can't patch this function for the whole method, else Synapse
        # will try to find the file when building the database initially.
        with mock.patch("os.listdir", mock.Mock(side_effect=fake_listdir)):
            with self.assertRaises(PrepareDatabaseException):
                # Synapse should think that there is an outstanding migration file due to
                # patching 'os.listdir' in the function decorator.
                #
                # We expect Synapse to raise an exception to indicate the master process
                # needs to apply this migration file.
                prepare_database(db_conn, db_pool.engine, self.hs.config)
Пример #2
0
    def test_not_upgraded_old_schema_version(self):
        """Test that workers don't start if the DB has an older schema version"""
        db_pool = self.hs.get_datastore().db_pool
        db_conn = LoggingDatabaseConnection(
            db_pool._db_pool.connect(),
            db_pool.engine,
            "tests",
        )

        cur = db_conn.cursor()
        cur.execute("UPDATE schema_version SET version = ?", (SCHEMA_VERSION - 1,))

        db_conn.commit()

        with self.assertRaises(PrepareDatabaseException):
            prepare_database(db_conn, db_pool.engine, self.hs.config)
Пример #3
0
    def test_rolling_back(self):
        """Test that workers can start if the DB is a newer schema version"""

        db_pool = self.hs.get_datastore().db_pool
        db_conn = LoggingDatabaseConnection(
            db_pool._db_pool.connect(),
            db_pool.engine,
            "tests",
        )

        cur = db_conn.cursor()
        cur.execute("UPDATE schema_version SET version = ?", (SCHEMA_VERSION + 1,))

        db_conn.commit()

        prepare_database(db_conn, db_pool.engine, self.hs.config)
Пример #4
0
def prepare_database(
        db_conn: LoggingDatabaseConnection,
        database_engine: BaseDatabaseEngine,
        config: Optional[HomeServerConfig],
        databases: Collection[str] = ("main", "state"),
):
    """Prepares a physical database for usage. Will either create all necessary tables
    or upgrade from an older schema version.

    If `config` is None then prepare_database will assert that no upgrade is
    necessary, *or* will create a fresh database if the database is empty.

    Args:
        db_conn:
        database_engine:
        config :
            application config, or None if we are connecting to an existing
            database which we expect to be configured already
        databases: The name of the databases that will be used
            with this physical database. Defaults to all databases.
    """

    try:
        cur = db_conn.cursor(txn_name="prepare_database")

        # sqlite does not automatically start transactions for DDL / SELECT statements,
        # so we start one before running anything. This ensures that any upgrades
        # are either applied completely, or not at all.
        #
        # (psycopg2 automatically starts a transaction as soon as we run any statements
        # at all, so this is redundant but harmless there.)
        cur.execute("BEGIN TRANSACTION")

        logger.info("%r: Checking existing schema version", databases)
        version_info = _get_or_create_schema_state(cur, database_engine)

        if version_info:
            user_version, delta_files, upgraded = version_info
            logger.info(
                "%r: Existing schema is %i (+%i deltas)",
                databases,
                user_version,
                len(delta_files),
            )

            # config should only be None when we are preparing an in-memory SQLite db,
            # which should be empty.
            if config is None:
                raise ValueError(
                    "config==None in prepare_database, but database is not empty"
                )

            # if it's a worker app, refuse to upgrade the database, to avoid multiple
            # workers doing it at once.
            if config.worker_app is not None and user_version != SCHEMA_VERSION:
                raise UpgradeDatabaseException(
                    OUTDATED_SCHEMA_ON_WORKER_ERROR %
                    (SCHEMA_VERSION, user_version))

            _upgrade_existing_database(
                cur,
                user_version,
                delta_files,
                upgraded,
                database_engine,
                config,
                databases=databases,
            )
        else:
            logger.info("%r: Initialising new database", databases)

            # if it's a worker app, refuse to upgrade the database, to avoid multiple
            # workers doing it at once.
            if config and config.worker_app is not None:
                raise UpgradeDatabaseException(EMPTY_DATABASE_ON_WORKER_ERROR)

            _setup_new_database(cur, database_engine, databases=databases)

        # check if any of our configured dynamic modules want a database
        if config is not None:
            _apply_module_schemas(cur, database_engine, config)

        cur.close()
        db_conn.commit()
    except Exception:
        db_conn.rollback()
        raise